diff --git a/lecture/misc/dungeon.md b/lecture/misc/dungeon.md deleted file mode 100644 index 1b2879af4..000000000 --- a/lecture/misc/dungeon.md +++ /dev/null @@ -1,914 +0,0 @@ ---- -author: Carsten Gips (HSBI) -title: "Frameworks: How-To Dungeon" ---- - -::: tldr -Der PM-Dungeon ist ein Framework zum Entwickeln von Rogue-like Dungeon-Crawlern, -also einfachen 2D-Spielen in Java. Das Framework bietet die wichtigsten benötigten -Grundstrukturen für ein Computer-Spiel: Es hat eine Game-Loop, kann Level generieren -und darstellen und hat eine Entity-Component-System-Struktur (ECS), über die die -Spielinhalte erstellt werden können. Im Hintergrund arbeitet die -Open-Source-Bibliothek libGDX. - -Sie können das Projekt direkt von GitHub clonen und über den im Projekt integrierten -Gradle-Wrapper starten. Dazu brauchen Sie Java 21 LTS (in einer 64-bit Version). Sie -können das Projekt als Gradle-Projekt in Ihre IDE laden. - -Die Starter-Klassen (z.B. `starter.Starter` im "dungeon"-Subprojekt oder -`starter.DevDungeon` im "devDungeon"-Subprojekt) sind die zentralen Einstiegspunkte. -Hier finden Sie "unseren" Teil der Game-Loop (der in der eigentlichen Game-Loop von -libGDX aufgerufen wird), hier finden Sie die Konfiguration und die `main()`-Methode. - -Im ECS werden die im Spiel befindlichen Elemente als *Entitäten* modelliert. Diese -Entitäten sind lediglich Container für *Components*, die dann ihrerseits die -entsprechenden Eigenschaften der Entitäten modellieren. Entitäten haben -normalerweise über die Components hinaus keine weiteren Eigenschaften (Attribute, -Methoden). Das Game kennt alle zum aktuellen Zeitpunkt "lebenden" Entitäten. - -Components gruppieren Eigenschaften, beispielsweise für Positionen oder -Lebenspunkte. Components haben normalerweise keine Methoden (halten also nur -Werte/Attribute). Jede Component-Instanz ist immer einer konkreten Entität -zugeordnet und kann ohne diese nicht existieren. - -*Systeme* implementieren das Verhalten im ECS. Das Game kennt alle aktiven Systeme -und ruft in jedem Durchlauf der Game-Loop die `execute()`-Methode der Systeme auf. -Üblicherweise holt sich dann ein System alle Entitäten vom Game und iteriert darüber -und fragt ab, ob die betrachtete Entität die notwendigen Components hat - falls ja, -dann kann das System auf dieser Entität die entsprechenden Operationen ausführen -(Animation, Bewegung, ...); falls nein, wird diese Entität ignoriert und mit der -Iteration fortgefahren. - -Wir programmieren in dieser Einheit einen einfachen Helden. Der Held ist eine -`Entity` und braucht verschiedene Components, um im Spiel angezeigt zu werden und -bewegt werden zu können. -::: - -::: youtube -- [VL How-To Dungeon: Teil 1 (Überblick)](https://youtu.be/kGKDcKIDHok) -- [VL How-To Dungeon: Teil 2 (Hero)](https://youtu.be/uPNt7OWpMBs) -- [VL How-To Dungeon: Teil 3 (Monster und - Feuerbälle)](https://youtu.be/_YVYoFgvU04) -::: - - - -# How-To Dungeon - -In diesem Semester werden Sie im Praktikum schrittweise Erweiterungen in -verschiedenen "fertigen" Rogue-like Computerspielen programmieren und dabei -(hoffentlich) die Methoden aus der Vorlesung einsetzen können. - -Das Projekt "PM-Dungeon" stellt wichtige Bausteine für das Spiel bereit, -beispielsweise eine Game-Loop und eine API für das Generieren und Benutzen von -Leveln und vieles andere mehr. Im Hintergrund werkelt das etablierte -Open-Source-Spieleframework [libGDX](https://libgdx.com). - -Wir werden uns in diesem How-To einen Überblick verschaffen und einen ersten -Einstieg versuchen: Wir programmieren einen einfachen Helden. - -# Projekt PM-Dungeon - -Das Projekt PM-Dungeon entstand in verschiedenen Forschungsprojekten und wurde (und -wird) aktiv von Studierenden und wissenschaftlichen Mitarbeitern am Campus Minden -entwickelt. - -Zuletzt lief das Forschungsprojekt "Dungeon", gefördert durch die [Stiftung für -Innovation in der Hochschullehre](https://stiftung-hochschullehre.de) im ["Freiraum -2022"](https://stiftung-hochschullehre.de/foerderung/freiraum2022/). Dabei sollten -diesmal nicht die Studierenden selbst Code schreiben, sondern die Lehrenden sollen -Aufgaben in einer speziellen (von uns entwickelten) Programmiersprache schreiben -(können), woraus dann ein fertiges Dungeon-Spiel generiert wird (mit der Aufgabe als -Quest o.ä. im Dungeon eingebettet) und die Studierenden können durch das Spielen die -Aufgaben lösen. - -Sie werden merken, dass trotz klarer Richtlinien und Ideen die Entwicklung in der -Praxis doch nicht so einfach ist und dass viele Dinge immer wieder geübt und -erinnert werden müssen: Namen von Klassen und Methoden, sinnvolles Javadoc, -Dokumentation jenseits des Javadoc, aber auch Commit-Messages und PR-Summaries. - -# Installation des Frameworks - -Sie finden das Projekt auf GitHub: -[github.com/Dungeon-CampusMinden/Dungeon](https://github.com/Dungeon-CampusMinden/Dungeon). - -![](images/screenshot_dungeon_clone.png) - -Laden Sie sich den Quellcode herunter, um damit in der IDE arbeiten zu können. -Prinzipiell gibt es viele verschiedene Wege, in diesem Tutorial laden wir es per Git -in der Konsole herunter: - -``` sh -git clone git@github.com:Dungeon-CampusMinden/Dungeon.git pm-dungeon -``` - -Dabei entsteht der Ordner `pm-dungeon/` mit dem Dungeon-Projekt als Inhalt. - -**WICHTIG**: Achten Sie bitte darauf, dass im Projektpfad **keine Leerzeichen** und -keine Sonderzeichen (Umlaute o.ä.) vorkommen! Dies kann zu seltsamen Fehler führen. -Bitte auch darauf achten, dass Sie als JDK ein **Java SE 21 (LTS)** verwenden. - -# Java: Java SE 21 (LTS) - -Wir benutzen im Dungeon-Projekt die aktuelle LTS-Version des JDK, d.h. **Java SE 21 -(LTS)**. Sie können sich das JDK bei -[Oracle](https://www.oracle.com/java/technologies/downloads/) herunterladen oder -[Alternativen](https://code.visualstudio.com/docs/languages/java#_install-a-java-development-kit-jdk) -ausprobieren. Bitte unbedingt die jeweilige 64-bit Version nutzen! - -In der Konsole sollte - -``` sh -java -version -``` - -ungefähr diese Ausgabe erzeugen (ignorieren Sie die Minor-Version, wichtig ist -Major-Version: 21 bzw. "LTS"): - - java version "21.0.3" 2024-04-16 LTS - Java(TM) SE Runtime Environment (build 21.0.3+7-LTS-152) - Java HotSpot(TM) 64-Bit Server VM (build 21.0.3+7-LTS-152, mixed mode, sharing) - -# Erster Test - -Für einen ersten Test gehen Sie in der Konsole in den vorhin erzeugten neuen Ordner -`pm-dungeon/` und führen Sie dort den Befehl - -``` sh -./gradlew game:runBasicStarter -``` - -aus. Dabei sollte das (mitgelieferte) Build-Tool [Gradle](https://gradle.org/) -starten und die benötigten Java-Bibliotheken herunterladen und schließlich das Spiel -in einer Minimalversion starten - Sie sollten also ein Level sehen. - -![](images/screenshot_dungeon_defaultlevel.png){width="80%"} - -Dies dauert je nach Internetanbindung etwas - beim nächsten Start geht es dann aber -deutlich schneller, weil ja bereits alles da ist. - -# Import in der IDE - -Importieren Sie das Projekt als Gradle-basiertes Projekt, dann übernimmt die IDE die -Konfiguration für Sie. - -![](images/screenshot_dungeon_import_intellij.png){width="80%"} - -![](images/screenshot_dungeon_import_intellij_asgradle.png){width="40%"} - -Über das Gradle-Menü können Sie nun in der IDE den "runBasicStarter"-Task (Menüpunkt -"game") starten, und es erscheint wieder ein minimales Level. - -# Überblick über die (Sub-) Projekte - -Sie finden im Package-Explorer eine Reihe von Unterprojekten (Gradle-Subprojekte). -Für PR2 ist eigentlich nur das Subprojekt -["devDungeon"](https://github.com/Dungeon-CampusMinden/dev-dungeon/tree/master/devDungeon) -relevant sowie die Dokumentation in den verschiedenen `doc/`-Ordnern (die derzeit -leider noch eine ziemliche Baustelle ist). - -![](images/screenshot_dungeon_intellij_gradle.png) - -DevDungeon stellt ein (mehr oder weniger fertiges) Spiel dar, das von Studierenden -erstellt wurde ([\@Flamtky](https://github.com/Flamtky)). Dieses Spiel nutzen wir an -einigen Stellen im Praktikum. - -Die Basis für dieses Spiel stellt das Dungeon-Framework dar, welches in den -Gradle-Subprojekten -["game"](https://github.com/Dungeon-CampusMinden/Dungeon/tree/master/game) und -["dungeon"](https://github.com/Dungeon-CampusMinden/Dungeon/tree/master/dungeon) zu -finden ist. Game stellt dabei eine Art minimale Basis zum Programmieren eigener -Spiele dar (alle Klassen im Package `core`), und Dungeon erweitert diese Basis und -fügt einige häufig benötigte Elemente und weitere Texturen (Package `contrib`) -hinzu. Zusätzlich gibt es hier noch einige Klassen für die DSL, was für PR2 aber -nicht relevant ist. - -Das Subprojekt -["blockly"](https://github.com/Dungeon-CampusMinden/Dungeon/tree/master/blockly) ist -die Einbindung einer blockbasierten Programmiersprache in das Dungeon-Framework und -spielt für PR2 ebenfalls keine Rolle. - -Die Strukturen in allen Sub-Projekten ist ähnlich: Sie finden unter -`/src/` die Java-Packages und in `/assets/` vordefinierte -Texturen und Soundfiles sowie Crafting-Rezepte (beispielsweise für Boden, Wände und -den Hero). Alle Sourcen sind (mehr oder weniger) mit Javadoc dokumentiert, -zusätzlich gibt es jeweils in `/doc/` weitere Anleitungen und Hinweise. - -Für die Aufgaben im Praktikum starten Sie am besten zunächst beim relevanten Code im -Sub-Projekt DevDungeon. Schauen Sie sich die für die Aufgabe benutzten Klassen und -deren Javadoc an. In der Regel nutzen diese auch Klassen aus Dungeon und Game, deren -Aufbau und Javadoc Sie sich ebenfalls anschauen sollten. Zusätzlich gibt es für Game -und Dungeon noch weitere Dokumentation in den `doc/`-Ordnern. - -# Überblick über die Java-Strukturen - -![](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/dungeon/doc/img/gameloop.png?raw=true) - -Jedes Spiel besteht aus einer Game-Loop, die je nach Konfiguration 30 Mal oder 60 -Mal pro Sekunde ausgeführt wird. Diese Game-Loop wird mit Hilfe der -`Game#run()`-Methode gestartet und die Kontrolle geht dabei vollständig an libGDX -über. Im Wesentlichen werden pro Durchlauf ("Frame") die Aktionen berechnet und das -Spielfeld neu gezeichnet. Alle Aktionen im Spiel, etwa das Bewegen von -Spielelementen oder das Berechnen von Angriffen o.ä., werden über sogenannte Systeme -berechnet. Diese werden einmal pro Frame aufgerufen und bestimmen den neuen Zustand -(Position, Animation, Stats, ...) der Spielelemente, die dann beim nächsten Rendern -im Spiel angezeigt werden. - -![](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/dungeon/doc/img/ecs.png?raw=true) - -Die Klasse `core.Game` ist der zentrale Einstiegspunkt. Hier werden alle wichtigen -Dinge konfiguriert, und es gibt die `Game#run()`-Methode, die das Spiel startet. -Zusätzlich gibt es weitere Methoden, die für Sie relevant sind: - -- `Game#userOnSetup()`: Diese Methode wird einmal beim Start des Spiels aufgerufen - und kann für die Konfiguration und Initialisierung der verschiedenen Systeme - genutzt werden. Hier wird beispielsweise u.a. auch das erste Level geladen. -- `Game#userOnFrame()`: Diese Methode wird zu Beginn eines jeden Frame aufgerufen, - noch bevor die `execute()`-Methode der verschiedenen Systeme aufgerufen wird. -- `Game#userOnLevelLoad()`: Diese Methode wird aufgerufen, wenn ein Level geladen - wird. Hier können Sie später die Entitäten erstellen, die initial im Level - verteilt werden sollen. - -Es gibt noch eine ganze Reihe von Packages, beispielsweise `core.Component` mit -verschiedenen wichtigen Components oder `core.level` mit Klassen zum Generieren -zufälliger neuer Level und zum Laden und zum Zugriff (wo bin ich und warum?) oder -`core.systems` mit den Systemen, die bestimmte Dinge im Spiel managen. Die -Gliederung in Entitäten (*entities*), Komponenten (*components*) und Systeme -(*systems*) nennt sich auch "ECS-Architektur" (zu ECS später mehr). - -Sie finden im ["Quickstart: How to -Dungeon"](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/game/doc/quickstart.md) -eine gute Anleitung, die auf die Strukturen tiefer eingeht. - -# Mein Held - -Um einen besseren Blick in das System zu bekommen, erstellen wir schrittweise einen -eigenen einfachen Helden. - -Legen Sie sich im `starter`-Package eine neue Klasse an, mit der Sie das Spiel -konfigurieren und starten können: - -``` java -package starter; -import core.Game; - -public class Main { - public static void main(String... args) { - // Start the game loop - Game.run(); - } -} -``` - -In IntelliJ können Sie nun die `main()`-Funktion direkt ausführen, dazu wird im -Hintergrund die vorhandene Gradle-Konfiguration genutzt. Mit anderen IDEs -funktioniert das vielleicht nicht direkt, dann erweitern Sie einfach die -Gradle-Konfiguration um einen entsprechenden Task: - -``` groovy -tasks.register('run', JavaExec) { - mainClass = 'starter.Main' - classpath = sourceSets.main.runtimeClasspath -} -``` - -# Einschub: ECS oder Entities, Components und Systems - -Der Held ist ein Element im Spiel. Diese Struktur muss geeignet modelliert werden. - -Unser Dungeon implementiert dabei eine Variante eines [Entity Component System -(*ECS*)](https://en.wikipedia.org/wiki/Entity_component_system) und folgt damit -"großen Vorbildern" wie beispielsweise -[Unity](https://learn.unity.com/tutorial/entity-component-system). - -Neben verschiedenen Hilfsstrukturen gibt es dabei nur **Entitäten**, **Komponenten** -und **Systeme**. Hier werden sämtliche Informationen und Verhalten modelliert. - -## Entity - -Die Idee dahinter ist: Alle Elemente im Spiel werden als *Entität* realisiert, d.h. -der Held und die Monster und die Items, die man so finden kann, sind alles -Entitäten. Sogar Feuerbälle sind letztlich Entitäten. (Im Prinzip könnten sogar die -Boden- und Wandkacheln Entitäten sein - sind es aus Effizienzgründen aktuell aber -nicht.) - -Eine Entität an sich kann erst einmal nichts und dient nur als Container für -*Components*. - -Das Spiel kennt alle zu einem Zeitpunkt vorhandenen Entitäten, diese müssen per -`Game#add` registriert werden. Man kann die Entitäten über die API abrufen -(`Game#allEntities`, `Game#find` und `Game#hero`). - -Unsere Basisklasse für Entitäten ist aktuell `core.Entity`. - -## Component - -Components bündeln bestimmte Werte einer Entität für bestimmte Zwecke, d.h. statt -der Attribute in einer Klasse (Entität) nutzen wir hier eine weitere Kapselung. - -Beispielsweise könnte man die Lebenspunkte u.ä. in einer `HealthComponent` verpacken -und dann in einer Entität speichern. Oder man könnte in einer `VelocityComponent` -hinterlegen, wie schnell eine Entität in x- und in y-Richtung bewegt werden kann -(Wände würden dabei einfach den Wert 0 bekommen). Oder man könnte in einer -`PositionComponent` speichern, wo die Entität gerade ist. Schauen Sie einfach mal in -die Packages `core.components` und `contrib.components`. - -Wichtig ist: Eine Instanz einer Component ist immer an eine Entität gekoppelt, eine -Component ohne (Bindung an eine) Entität ist sinnfrei. Andersherum kann eine Entität -immer nur eine einzige Instanz einer bestimmten Component (eines Component-Typs) -haben, also beispielsweise nicht zwei Objekte vom Typ `PositionComponent`. - -Components speichern vor allem Werte und haben nur in Ausnahmefällen eigenes -Verhalten. - -Das Basisinterface für Components ist derzeit `core.Component`. - -## System - -Mit Entitäten und passenden Components, über die wir die Eigenschaften ausdrücken, -können wir bereits Spielelemente im Dungeon repräsentieren. - -Für die Bewegung und Interaktion sorgen nun passende Systeme. Das Spiel kennt alle -Systeme (diese werden einmal beim Start im Spiel per `Game#add` registriert) und -ruft in der Game-Loop pro Frame deren `execute()`-Methode auf. In der Regel -iterieren die Systeme beim Ausführen der `execute()`-Methode über die Entitäten des -Spiels (via `Game#allEntities`), suchen sich Entitäten mit bestimmten Components -heraus und bearbeiten den Zustand dieser Components. - -Dabei könnte beispielsweise ein `HealthSystem` sich alle Entitäten filtern, deren -`HealthComponent` unterhalb einer kritischen Schwelle liegen und diese rot anmalen -lassen, d.h. in der `DrawComponent` wird die Textur ("Animation") ausgetauscht. Oder -ein `PlayerSystem` könnte dafür sorgen, dass die Eingaben auf der Tastatur geeignet -an den Helden weitergegeben werden und (über andere Systeme) in eine Bewegung oder -Kampf o.ä. umgewandelt werden. - -Sie finden unsere Systeme in den Packages `core.systems` und `contrib.systems`, und -die Basisklasse ist derzeit `core.System` - falls Sie einmal eigene Systeme -implementieren wollen. ([vgl. auch -Doku](https://github.com/Dungeon-CampusMinden/Dungeon/blob/master/game/doc/create_own_content.md)) - -# Nun aber Helden! - -## Ein Held ist eine Entität - -Also legen wir nun endlich einen neuen Helden als Instanz von `core.Entity` an und -registrieren diese Entität im Spiel: - -``` java -public class Main { - public static void main(String... args) { - - // Add some one-time configuration - Game.userOnSetup( - () -> { - Entity hero = new Entity("Hero"); - Game.add(hero); - }); - - // Start the game loop - Game.run(); - } -} -``` - -Der in der Methode `Game#userOnSetup` übergebene Lamda-Ausdruck wird (später) -einmalig beim Start der Game-Loop von libGDX aufgerufen. Auf diese Weise können wir -unseren Helden ins Spiel bekommen. (Alle anderen Entitäten sollten Sie besser über -die Methode `Game#onLevelLoad` anlegen, also beim Laden eines neuen Levels.) - -Prinzipiell haben Sie damit alles, um das Spiel starten zu können. In der Praxis -sehen Sie aber keinen Helden: Der hat nämlich weder eine Position noch eine Textur, -kann also gar nicht angezeigt werden. - -## Wo bin ich grad? - -Der Held braucht eine Position. Dazu gibt es `core.components.PositionComponent`. -Fügen wir diese einfach dem Helden hinzu: - -``` java -public class Main { - public static void main(String... args) { - - // Add some one-time configuration - Game.userOnSetup( - () -> { - Entity hero = new Entity("Hero"); - - hero.add(new PositionComponent()); - - Game.add(hero); - }); - - // Start the game loop - Game.run(); - } -} -``` - -Wenn man keine Position mitgibt, wird einfach eine zufällige Position im Level -genutzt. Alternativ kann man eine eigene Position mitgeben. - -Im Dungeon existieren aktuell zwei Koordinatensysteme: `core.level.utils.Coordinate` -(Integer-basiert) und `core.utils.Point` (Float-basiert). Die Level werden als -Matrix von `Tile` (Boden, Wand, Loch, ...) gespeichert. Die Position dieser `Tile` -wird als `Coordinate` gespeichert, was dem Index des Tiles in der Matrix entspricht. -Entitäten können aktuell aber auch zwischen zwei Tiles oder schräg-links-oben auf -einem Tile stehen, dafür gibt es die Positionen als `Point`. Entsprechend könnte man -den neuen Helden bei `(0,0)` in das Level setzen: -`new PositionComponent(new Point(0, 0))` bzw. kurz `new PositionComponent(0f, 0f)` -(wobei diese Position möglicherweise nicht spielbar ist, da hier eine Wand oder -sogar nichts ist). - -Wenn Sie jetzt das Spiel starten, sehen Sie - immer noch nichts (außer den Wänden). -Hmmm. - -## Animateure - -Um den Held zeichnen zu können, brauchen wir eine Animation - also eine -`DrawComponent`. - -``` java -public class Main { - public static void main(String... args) { - - // Add some one-time configuration - Game.userOnSetup( - () -> { - Entity hero = new Entity("Hero"); - - hero.add(new PositionComponent()); - - try { - hero.add(new DrawComponent(new SimpleIPath("character/knight"))); - } catch (IOException e) { - System.err.println("Could not load textures for hero."); - throw new RuntimeException(e); - } - - hero.add(new CameraComponent()); - hero.add(new PlayerComponent()); - - Game.add(hero); - }); - - // Start the game loop - Game.run(); - } -} -``` - -In den Asset-Ordnern der Sub-Projekte Game und Dungeon gibt es bereits vordefinierte -Texturen. Im Beispiel wird (nur) im Sub-Projekt "game" gesucht (weil unsere -`Main`-Klasse dort liegt), und zwar in `/assets/character/knight/`. Dort -finden sich Unterordner für verschiedene Zustände des Ritters, und darin jeweils -einige Texturen (einfache kleine .png-Dateien), die als Animation in einem -bestimmten Zustand nacheinander abgespielt werden. Über den angegebenen (Teil-) Pfad -werden in `DrawComponent` automatisch die entsprechenden Animationen erzeugt und -geladen. Die Asset-Ordner sind in der Gradle-Konfiguration definiert. (Wenn Sie Ihre -`Main`-Klasse in Dungeon ansiedeln, stehen Ihnen automatisch die Texturen aus -Dungeon plus aus Game zur Verfügung.) - -Da es passieren kann, dass der übergebene Pfad nicht gefunden wird, muss hier mit -Exception-Handling gearbeitet werden. Wir geben hier erstmal eine Fehlermeldung aus -und propagieren eine neue `RuntimeException`, die letztlich dafür sorgt, dass das -Spiel abgebrochen würde. - -Zusätzlich brauchen wir für den Helden noch eine `CameraComponent`. Das -`core.systems.CameraSystem` wird dafür sorgen, dass die Entität mit dieser Component -immer im Fokus der Kamera ist. Da wir den Held später noch manuell steuern wollen, -bekommt er auch gleich noch eine `PlayerComponent`. - -Jetzt wackelt der Held auf der Stelle herum ... - -## Bewege mich - -Für die Bewegung ist das `VelocitySystem` zuständig. Dieses fragt in allen Entitäten -die `VelocityComponent` sowie die `PositionComponent` ab, berechnet die nächste neue -Position und speichert diese in der `PositionComponent`, und setzt bei tatsächlicher -Bewegung auch eine passende Bewegungsanimation in der `DrawComponent`. - -Das `PlayerSystem` und die `PlayerComponent` sorgen im Zusammenspiel für eine -Reaktion auf die Tastatureingaben. - -``` java -public class Main { - public static void main(String... args) { - - // Add some one-time configuration - Game.userOnSetup( - () -> { - Entity hero = new Entity("Hero"); - - hero.add(new PositionComponent()); - - try { - hero.add(new DrawComponent(new SimpleIPath("character/knight"))); - } catch (IOException e) { - System.err.println("Could not load textures for hero."); - throw new RuntimeException(e); - } - - hero.add(new CameraComponent()); - - hero.add(new VelocityComponent(5f, 5f)); - - PlayerComponent pc = new PlayerComponent(); - pc.registerCallback( - KeyboardConfig.MOVEMENT_UP.value(), - entity -> { - VelocityComponent vc = entity.fetch(VelocityComponent.class).get(); - vc.currentYVelocity(vc.yVelocity()); - }); - hero.add(pc); - - Game.add(hero); - }); - - // Start the game loop - Game.run(); - } -} -``` - -Die `VelocityComponent` wird im Konstruktor mit einer (maximalen) Geschwindigkeit in -x- und y-Richtung erzeugt. Nutzen Sie hier nicht zu große Werte - unter Umständen -reicht dann ein einziger Tastendruck, um einmal über das Spielfeld geschleudert zu -werden. - -Über die Methoden `VelocityComponent#xVelocity` und `VelocityComponent#yVelocity` -können Sie die Maximalgeschwindigkeit abfragen und auch setzen. Mit -`VelocityComponent#currentXVelocity` bzw. `VelocityComponent#currentYVelocity` holen -und setzen Sie dagegen die *aktuelle* Geschwindigkeit, die vom `VelocitySystem` zur -Berechnung der nächsten Position genutzt wird (wobei die Maximalgeschwindigkeit als -Obergrenze verwendet wird). - -Im Beispiel wird in der `PlayerComponent` des Helden der Taste "W" ein -Lambda-Ausdruck zugeordnet, der die `VelocityComponent` der Entität (also des -Helden) holt, die maximale Geschwindigkeit in y-Richtung ausliest und diese als -aktuelle Geschwindigkeit in y-Richtung setzt. Damit kann mit der Taste "W" der Held -nach oben laufen. - -*Anmerkung*: Das `entity.fetch(VelocityComponent.class)` liefert nicht direkt ein -`VelocityComponent`-Objekt zurück, sondern ein `Optional`. -Darüber sprechen wir (später) noch in der Lektion -["Optional"](../java-modern/optional.md). Für jetzt soll es zunächst genügen, dass -Sie das gewünschte "verpackte" Objekt mit der Methode `get()` aus dem `Optional` -wieder herausbekommen. - -*Anmerkung*: Das gezeigte Schema ist insofern typisch, als dass verschiedene Systeme -aus der Maximalgeschwindigkeit und weiteren Parametern die aktuelle Geschwindigkeit -berechnen und in der `VelocityComponent` einer Entität setzen. Das `VelocitySystem` -nutzt dann die aktuelle Geschwindigkeit für die tatsächliche Bewegung. Sie sollten -in der Praxis also die Methoden `VelocityComponent#currentXVelocity` bzw. -`VelocityComponent#currentYVelocity` eher nicht selbst aufrufen, sondern dies den -Systemen überlassen. Wenn Sie einen Geschwindigkeitsboost haben wollen, würde es bei -der obigen Konfiguration ausreichen, `VelocityComponent#xVelocity` und/oder -`VelocityComponent#yVelocity` zu setzen/zu erhöhen - den Rest übernehmen dann das -`PlayerSystem` und vor allem das `VelocitySystem` ... - -Nun sollten Sie Ihren Helden (nach oben) bewegen können. (Tipp: Probieren Sie "W".) - -*Hinweis*: Üblicherweise bearbeiten die Systeme bei der Iteration über alle -Entitäten nur diejenigen Entitäten, die alle benötigten Components aufweisen. - -# Walking mit System - -## Neue Monster - -Wie kann ich ein Monster beim Laden des Levels erzeugen? - -Beim Laden eines Levels wird der mit `Game#userOnLevelLoad` registrierte -Lambda-Ausdruck ausgeführt. Hier kann man beispielsweise ein neues Monster erzeugen -(lassen): - -``` java -public class Main { - public static void main(String... args) { - - // Add some one-time configuration - Game.userOnSetup( ... ); - - - // Create a new monster in every new level - Game.userOnLevelLoad(first -> { - Entity fb = new Entity("HUGO"); - - fb.add(new PositionComponent(Game.hero().get().fetch(PositionComponent.class).get().position())); - - try { - fb.add(new DrawComponent(new SimpleIPath("character/knight"))); - } catch (IOException e) { - System.err.println("Could not load textures for HUGO."); - throw new RuntimeException(e); - } - - VelocityComponent vc = new VelocityComponent(10f, 10f); - vc.currentYVelocity(vc.yVelocity()); - fb.add(vc); - - Game.add(fb); - }); - - - // Start the game loop - Game.run(); - } -} -``` - -Im Lambda-Ausdruck erzeugen wir hier einfach eine neue Entität und fügen dieser wie -vorhin beim Hero eine `DrawComponent` für die Anzeige sowie eine `PositionComponent` -und eine `VelocityComponent` für die Position und Bewegung hinzu, und am Ende -registrieren wir die Entität beim Spiel. - -Wenn man das Spiel jetzt startet, wird an der Position des Helden eine neue Entität -sichtbar (mit der selben Textur). - -Aber warum bewegt die neue Figur sich nicht? Wir haben doch eine `VelocityComponent` -hinzugefügt und eine aktuelle Geschwindigkeit gesetzt?! - -Wenn man in `VelocitySystem#execute` (bzw. die dort aufgerufene Methode -`VelocitySystem#updatePosition`) schaut, wird klar, dass die aktuelle -Geschwindigkeit zwar neu berechnet und gesetzt wird, aber dass ein "Reibungsfaktor" -(abhängig vom Feld, auf dem die Figur steht) eingerechnet wird und somit die -aktuelle Geschwindigkeit schnell auf Null geht. Der Hintergrund ist einfach: -Normalerweise soll eine Entität nicht einmal angeschubst werden und dann "ewig" -laufen, insbesondere bei Reaktion auf Tastatureingaben. Deshalb werden die Entitäten -kurz bewegt und bremsen dann wieder ab. Das Aufrechterhalten der Bewegung erfolgt -normalerweise über Systeme ... - -## Systems für das selbstständige Laufen - -Wir brauchen ein System, welches die aktuelle Geschwindigkeit einer Entität in jedem -Frame wieder auf den alten Wert setzt. Dazu leiten wir von `core.System` ab. -(Achtung: Es gibt auch eine Klasse `System` im JDK - hier müssen Sie genau -hinschauen!) - -``` java -import core.System; - -public class WalkerSystem extends System { - @Override - public void execute() { - entityStream().forEach(e -> { - VelocityComponent vc = e.fetch(VelocityComponent.class).get(); - vc.currentXVelocity(vc.xVelocity()); - vc.currentYVelocity(vc.yVelocity()); - }); - } -} - - -public class Main { - public static void main(String... args) { - - // Add some one-time configuration - Game.userOnSetup( ... ); - - // Create a new monster in every new level - Game.userOnLevelLoad( ... ); - - - // Register our new system - Game.add(new WalkerSystem()); - - - // Start the game loop - Game.run(); - } -} -``` - -Wir leiten also von `core.System` ab und implementieren die `execute`-Methode. Wir -holen uns dabei von jeder Entität die `VelocityComponent` und setzen die aktuelle -Geschwindigkeit neu auf die maximale Geschwindigkeit. Zusätzlich registrieren wir -das neue System im Spiel, damit es in jedem Frame einmal aufgerufen wird. - -Nun läuft das neue Monster los (bis es gegen eine Wand läuft). - -Aber der Held bewegt sich nun ebenfalls dauerhaft :( - -## Components für das selbstständige Laufen - -Das Problem ist, dass unser neues `WalkerSystem` **alle** Entitäten automatisch -bewegt. (Ein weiteres Problem ist, dass das `WalkerSystem` davon ausgeht, dass es -immer eine `VelocityComponent` gibt, was nicht unbedingt erfüllt ist!) - -Wir brauchen also noch eine Component, mit der wir die zu bewegenden Entitäten -markieren können. - -``` java -import core.System; -import core.Component; - -public class WalkerComponent implements Component {} - - -public class WalkerSystem extends System { - public WalkerSystem() { - super(WalkerComponent.class); - } - - @Override - public void execute() { - entityStream().forEach(e -> { - if (e.isPresent(WalkerComponent.class)) { - VelocityComponent vc = e.fetch(VelocityComponent.class).get(); - vc.currentXVelocity(vc.xVelocity()); - vc.currentYVelocity(vc.yVelocity()); - } - }); - } -} - - -public class Main { - public static void main(String... args) { - - // Add some one-time configuration - Game.userOnSetup( ... ); - - - // Create a new monster in every new level - Game.userOnLevelLoad(first -> { - Entity fb = new Entity("HUGO"); - - ... - - fb.add(new WalkerComponent()); - - Game.add(fb); - }); - - - // Register our new system - Game.add(new WalkerSystem()); - - // Start the game loop - Game.run(); - } -} -``` - -Die neue Component (`WalkerComponent`) ist einfach eine leere Klasse, die von -`core.Component` erbt. Wir brauchen keine Werte o.ä., die wir hier ablegen wollen - -eine leere Klasse reicht für das Beispiel. Dem neuen Monster geben wir diese neue -Component nun mit. - -Das `WalkerSystem` wird auch etwas ergänzt: Im Konstruktor rufen wir den -Super-Konstruktor auf und übergeben die `WalkerComponent`-Klasse - dies ist die -Component, für die sich das System interessiert. Zusätzlich legen wir noch eine -`if`-Abfrage um das Aktualisieren der aktuellen Geschwindigkeit: Der Block soll nur -dann ausgeführt werden, wenn die im aktuellen Schleifendurchlauf gerade betrachtete -Entität eine `WalkerComponent` hat. - -Nun läuft nur das neue Monster automatisch, der Held bleibt stehen und reagiert erst -auf Tastendrücke. Prima! - -Auf diese Weise können Sie beispielsweise den Monstern einen Gesundheitszustand -geben und diese bei zu schlechter Gesundheit "sterben" lassen (aus dem Spiel -entfernen). Sie könnten aber auch komplexere Dinge wie die Kollision zwischen zwei -Entitäten realisieren. - -Tatsächlich gibt es im Sub-Projekt "dungeon" (Package `contrib`) bereits eine -Vielzahl an Components und passenden Systems, die solche typischen Aufgaben bereits -realisieren. - -# Kämpfe wie ein NPC - -Wir haben beim Hero über das `PlayerComponent` eine Reaktion auf Tastatureingaben -implementiert. Hier könnte man einer Taste auch den Start einer neuen Entität -zuordnen, die sich dann automatisch bewegt. Man könnte also Feuerbälle schleudern -... - -``` java -public class Main { - public static void main(String... args) { - - - // Add some one-time configuration - Game.userOnSetup( () -> { - Entity hero = new Entity("Hero"); - - ... - - PlayerComponent pc = new PlayerComponent(); - pc.registerCallback(KeyboardConfig.FIRST_SKILL.value(), entity -> { - Entity fb = new Entity("Fireball"); - - fb.add(new PositionComponent(entity.fetch(PositionComponent.class).get().position())); - - try { - fb.add(new DrawComponent(new SimpleIPath("character/knight"))); - } catch (IOException e) { - System.err.println("Could not load textures for fireball."); - throw new RuntimeException(e); - } - - fb.add(new VelocityComponent(2f, 2f)); - - fb.add(new WalkerComponent()); - - Game.add(fb); - }, false); - - Game.add(hero); - }); - - - // Create a new monster in every new level - Game.userOnLevelLoad( ... ); - - // Register our new system - Game.add(new WalkerSystem()); - - // Start the game loop - Game.run(); - } -} -``` - -Wir registrieren einfach die Taste `FIRST_SKILL` (das ist ein "Q") in der -`PlayerComponent`. Im hinterlegten Lamda-Ausdruck wird eine neue Entität erzeugt mit -einer `WalkerComponent`, also ganz analog zu dem neuen Monster vorhin beim Laden -eines neuen Levels. Zusätzlich wird hier noch ein dritter Parameter mit dem Wert -`false` mitgegeben: Die `PlayerComponent` wird in *jedem* Frame ausgewertet - wenn -die Taste "Q" also über mehrere Frames hinweg gedrückt ist (was sehr wahrscheinlich -ist), würde in jedem dieser Frames je eine neue Entität erzeugt und losgeschickt. -Über diesen dritten Parameter können wir steuern, dass genau das nicht passiert. Man -muss also die Taste "Q" zunächst wieder loslassen und dann erneut drücken, um noch -einen Feuerball zu erzeugen und auf den Weg zu schicken. Als Textur habe ich einfach -die im Sub-Projekt "game" vorhandene Textur für die Heros genommen - im Sub-Projekt -"dungeon" gibt es dagegen auch Feuerbälle u.ä., aber dann müsste die Klasse auch in -dieses Sub-Projekt umgezogen werden. - -Unser Feuerball kann leider nichts, außer sich automatisch zu bewegen. Man könnte -nun noch ein `CollisionSystem` entwickeln, welches Entitäten immer paarweise auf -ihre Positionen vergleicht und eine Kollision feststellt, wenn sich die Entitäten zu -nah kommen und diese Information in einer `CollisionComponent` speichern (wer mit -wem und wann). Dann könnte man noch ein `HealthSystem` bauen, welches eine -`HealthComponent` aktualisiert. Zusätzlich könnte man ein `FightSystem` schreiben, -welches bei einer Kollision der getroffenen Entität (zufälligen?) Schaden zufügt, -also die Werte in ihrer `HealthComponent` reduziert. (Alternativ könnte das -`CollisionSystem` bei Kollision einen in der `CollisionComponent` gespeicherten -Lambda-Ausdruck ausführen.) ... Die einzelnen Klassen interagieren also nicht direkt -miteinander, sondern immer über den Umweg der Systems und Components. - -All diese (und viele weitere) Components und Systems gibt es bereits im Package -`contrib` im Sub-Projekt -["dungeon"](https://github.com/Dungeon-CampusMinden/Dungeon/tree/master/dungeon). - -# Wrap-Up - -::: notes -Damit endet der kurze Ausflug in den Dungeon. -::: - -In einem ECS haben wir Entities, Components und Systems. - -- Die Entitäten sind nur Hüllen und gruppieren verschiedene Components. -- In diesen Components werden die Werte für die jeweiligen Zustände gehalten. -- Die Systems werden in jedem Durchlauf der Game-Loop aufgerufen und führen dabei - ihre `execute()`-Methode aus. Typischerweise iterieren die Systeme dabei über - alle Entitäten und verändern die Components der Entitäten. - -Denken Sie daran, dass alles in einer Game-Loop läuft, die 30x oder 60x pro Sekunde -aufgerufen wird. Sie können in der Regel keine direkte Interaktion zwischen -verschiedenen Objekten realisieren, sondern müssen immer den Weg über die Systems -gehen. - -Schauen Sie gern in die vorhandenen Klassen und Packages und in die Dokumentation -hinein: - -- Klassen in `game/src/` und `dungeon/src` -- Dokumentation unter `game/doc/` und `dungeon/doc/` - -::: notes -Die Javadoc-Kommentare sollten Ihnen erste Ideen zur Funktionsweise geben (auch wenn -für das angestrebte Ideal noch einiges an Arbeit notwendig ist). Schauen Sie gern -die Dokumentation unter `game/doc/` und `dungeon/doc/` an, die im Laufe des -Semesters schrittweise weiter wachsen wird. -::: - -\bigskip - -Anregungen für **Spielideen** [können Sie beispielsweise in den folgenden Videos -finden:]{.notes} - -- [Shattered Pixel Dungeon Rogue Beginners Guide - Playthrough](https://youtu.be/qoc_tDN0QC4) -- [Shattered Pixel Dungeon Duelist Update!](https://youtu.be/LgSjUWjQg0s) - -::: notes -Viel Spass im PM-Dungeon! -::: - -::: outcomes -- k2: Überblick über die wichtigsten Strukturen im PM-Dungeon -- k2: Aufbau eines ECS: Entitäten, Komponenten, Systeme -- k3: Herunterladen und installieren des PM-Dungeon -- k3: Laden in der IDE -- k3: Erstellen eines Helden mit Animation und Bewegung -::: diff --git a/lecture/misc/images/checklisteMotivation.png b/lecture/misc/images/checklisteMotivation.png deleted file mode 100644 index cbd94dd80..000000000 Binary files a/lecture/misc/images/checklisteMotivation.png and /dev/null differ diff --git a/lecture/misc/images/checklisteMotivationFarbig.png b/lecture/misc/images/checklisteMotivationFarbig.png deleted file mode 100644 index 66fec4bc1..000000000 Binary files a/lecture/misc/images/checklisteMotivationFarbig.png and /dev/null differ diff --git a/lecture/misc/images/frameworkVSlib.uxf b/lecture/misc/images/frameworkVSlib.uxf deleted file mode 100644 index 9b2fceb4b..000000000 --- a/lecture/misc/images/frameworkVSlib.uxf +++ /dev/null @@ -1,24 +0,0 @@ -10Space for diagram notesUMLPackage120140420450Framework --- -bg=#CAE1FF -layer=1 -transparency=0UMLClass14020037050MyCode -bg=#66CDAA -layer=2 -transparency=0UMLClass14050037030MyCode -bg=#66CDAA -layer=2 -transparency=0UMLPackage560140380450MyCode --- -bg=#66CDAA -transparency=0 -UMLClass64026024050Library-Function -bg=#CAE1FF -layer=2 -transparency=0UMLClass64048024050Library-Function -bg=#CAE1FF -layer=2 -transparency=0UMLClass14035037050MyCode -bg=#66CDAA -layer=1 -transparency=0 diff --git a/lecture/misc/images/frameworksVSlib.png b/lecture/misc/images/frameworksVSlib.png deleted file mode 100644 index 46e590866..000000000 Binary files a/lecture/misc/images/frameworksVSlib.png and /dev/null differ diff --git a/lecture/misc/images/screenshot_dungeon_clone.png b/lecture/misc/images/screenshot_dungeon_clone.png deleted file mode 100644 index c52260957..000000000 Binary files a/lecture/misc/images/screenshot_dungeon_clone.png and /dev/null differ diff --git a/lecture/misc/images/screenshot_dungeon_defaultlevel.png b/lecture/misc/images/screenshot_dungeon_defaultlevel.png deleted file mode 100644 index 492aad2c7..000000000 Binary files a/lecture/misc/images/screenshot_dungeon_defaultlevel.png and /dev/null differ diff --git a/lecture/misc/images/screenshot_dungeon_import_intellij.png b/lecture/misc/images/screenshot_dungeon_import_intellij.png deleted file mode 100644 index 0954fedbe..000000000 Binary files a/lecture/misc/images/screenshot_dungeon_import_intellij.png and /dev/null differ diff --git a/lecture/misc/images/screenshot_dungeon_import_intellij_asgradle.png b/lecture/misc/images/screenshot_dungeon_import_intellij_asgradle.png deleted file mode 100644 index 6e2a25514..000000000 Binary files a/lecture/misc/images/screenshot_dungeon_import_intellij_asgradle.png and /dev/null differ diff --git a/lecture/misc/images/screenshot_dungeon_intellij_gradle.png b/lecture/misc/images/screenshot_dungeon_intellij_gradle.png deleted file mode 100644 index dd13b4a4f..000000000 Binary files a/lecture/misc/images/screenshot_dungeon_intellij_gradle.png and /dev/null differ diff --git a/lecture/misc/readme.md b/lecture/misc/readme.md deleted file mode 100644 index 68f9b96ab..000000000 --- a/lecture/misc/readme.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -no_beamer: true -no_pdf: true -title: Verschiedenes ---- - diff --git a/lecture/misc/src/dungeon/demo/Main.java b/lecture/misc/src/dungeon/demo/Main.java deleted file mode 100644 index a1f272289..000000000 --- a/lecture/misc/src/dungeon/demo/Main.java +++ /dev/null @@ -1,106 +0,0 @@ -package demo; - -import core.Entity; -import core.Game; -import core.components.*; -import core.configuration.KeyboardConfig; -import core.utils.components.path.SimpleIPath; -import java.io.IOException; - -public class Main { - public static void main(String... args) { - - // Add some one-time configuration - Game.userOnSetup( - () -> { - Entity hero = new Entity(); - - hero.add(new PositionComponent()); - - try { - hero.add(new DrawComponent(new SimpleIPath("character/knight"))); - } catch (IOException e) { - System.err.println("Could not load textures for hero."); - throw new RuntimeException(e); - } - - hero.add(new CameraComponent()); - hero.add(new PlayerComponent()); - - VelocityComponent vc = new VelocityComponent(5f, 5f); - hero.add(vc); - vc.currentXVelocity(10f); - vc.previousXVelocity(10f); - - PlayerComponent pc = new PlayerComponent(); - pc.registerCallback( - KeyboardConfig.MOVEMENT_UP.value(), - entity -> { - vc.currentYVelocity(vc.yVelocity()); - }); - pc.registerCallback( - KeyboardConfig.MOVEMENT_DOWN.value(), entity -> vc.currentYVelocity(-vc.yVelocity())); - pc.registerCallback( - KeyboardConfig.MOVEMENT_LEFT.value(), entity -> vc.currentXVelocity(-vc.xVelocity())); - pc.registerCallback( - KeyboardConfig.MOVEMENT_RIGHT.value(), entity -> vc.currentXVelocity(vc.xVelocity())); - hero.add(pc); - - pc.registerCallback( - KeyboardConfig.FIRST_SKILL.value(), - entity -> { - Entity fb = new Entity("Fireball"); - - fb.add( - new PositionComponent(entity.fetch(PositionComponent.class).get().position())); - - try { - fb.add(new DrawComponent(new SimpleIPath("character/knight"))); - } catch (IOException e) { - System.err.println("Could not load textures for fireball."); - throw new RuntimeException(e); - } - - fb.add(new VelocityComponent(2f, 2f)); - - fb.add(new WalkerComponent()); - - Game.add(fb); - }, - false); - - Game.add(hero); - }); - - // Create a new monster in every new level - Game.userOnLevelLoad( - first -> { - Entity fb = new Entity("HUGO"); - - fb.add( - new PositionComponent( - Game.hero().get().fetch(PositionComponent.class).get().position())); - - try { - fb.add(new DrawComponent(new SimpleIPath("character/knight"))); - } catch (IOException e) { - System.err.println("Could not load textures for fireball."); - throw new RuntimeException(e); - } - - VelocityComponent vc = new VelocityComponent(10f, 10f); - vc.currentYVelocity(vc.yVelocity()); - fb.add(vc); - - fb.add(new WalkerComponent()); - - Game.add(fb); - }); - - // Register our new system - Game.add(new WalkerSystem()); - - // Start the game loop - Game.run(); - } -} diff --git a/lecture/misc/src/dungeon/demo/WalkerComponent.java b/lecture/misc/src/dungeon/demo/WalkerComponent.java deleted file mode 100644 index a2b9dc155..000000000 --- a/lecture/misc/src/dungeon/demo/WalkerComponent.java +++ /dev/null @@ -1,5 +0,0 @@ -package demo; - -import core.Component; - -public class WalkerComponent implements Component {} diff --git a/lecture/misc/src/dungeon/demo/WalkerSystem.java b/lecture/misc/src/dungeon/demo/WalkerSystem.java deleted file mode 100644 index 1a494a373..000000000 --- a/lecture/misc/src/dungeon/demo/WalkerSystem.java +++ /dev/null @@ -1,23 +0,0 @@ -package demo; - -import core.System; -import core.components.VelocityComponent; - -public class WalkerSystem extends System { - public WalkerSystem() { - super(WalkerComponent.class); - } - - @Override - public void execute() { - entityStream() - .forEach( - e -> { - if (e.isPresent(WalkerComponent.class)) { - VelocityComponent vc = e.fetch(VelocityComponent.class).get(); - vc.currentXVelocity(vc.xVelocity()); - vc.currentYVelocity(vc.yVelocity()); - } - }); - } -} diff --git a/lecture/misc/src/dungeon/notes.txt b/lecture/misc/src/dungeon/notes.txt deleted file mode 100644 index 288585e5c..000000000 --- a/lecture/misc/src/dungeon/notes.txt +++ /dev/null @@ -1,7 +0,0 @@ -Diesen Source-Tree in das Sub-Projekt "game" vom Dungeon-Projekt legen zum Ausführen. - -Zusätzlich muss noch die Konstante FIRST_SKILL in game/src/core/configuration/KeyboardConfig.java ergänzt werden: - -public static final ConfigKey FIRST_SKILL = new ConfigKey<>(new String[] {"foo", "bar"}, new ConfigIntValue(Input.Keys.Q)); - - diff --git a/lecture/misc/src/javalin/build.gradle b/lecture/misc/src/javalin/build.gradle deleted file mode 100644 index 5dbe9c2bf..000000000 --- a/lecture/misc/src/javalin/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - id 'java' - id "com.diffplug.spotless" version "6.5.0" -} - -repositories { - mavenCentral() -} - -dependencies { - implementation 'io.javalin:javalin:4.5.0' - implementation 'org.slf4j:slf4j-simple:1.8.0-beta4' -} - -spotless { - java { - googleJavaFormat().aosp() - } -} - -task javalinHelloWorld(dependsOn: classes, type: JavaExec) { - mainClass = "JavalinHelloWorld" - classpath = sourceSets.main.runtimeClasspath -} - -task javalinRandomNumber(dependsOn: classes, type: JavaExec) { - mainClass = "JavalinRandomNumber" - classpath = sourceSets.main.runtimeClasspath -} diff --git a/lecture/misc/src/javalin/gradle.properties b/lecture/misc/src/javalin/gradle.properties deleted file mode 100644 index cf2663841..000000000 --- a/lecture/misc/src/javalin/gradle.properties +++ /dev/null @@ -1,13 +0,0 @@ - -org.gradle.daemon=true -org.gradle.configureondemand=false -org.gradle.jvmargs=-Xms128m -Xmx2048m \ - --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED -# The "add-exports" options are a workaround to use the "google-java-format" plugin with Java 17 and Gradle 7.2. -# See also here: -# https://github.com/google/google-java-format#jdk-16 -# https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format \ No newline at end of file diff --git a/lecture/misc/src/javalin/gradle/wrapper/gradle-wrapper.jar b/lecture/misc/src/javalin/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b953..000000000 Binary files a/lecture/misc/src/javalin/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/lecture/misc/src/javalin/gradle/wrapper/gradle-wrapper.properties b/lecture/misc/src/javalin/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 0aaefbcaf..000000000 --- a/lecture/misc/src/javalin/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/lecture/misc/src/javalin/gradlew b/lecture/misc/src/javalin/gradlew deleted file mode 100755 index f5feea6d6..000000000 --- a/lecture/misc/src/javalin/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/lecture/misc/src/javalin/gradlew.bat b/lecture/misc/src/javalin/gradlew.bat deleted file mode 100755 index 9d21a2183..000000000 --- a/lecture/misc/src/javalin/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/lecture/misc/src/javalin/src/main/java/JavalinHelloWorld.java b/lecture/misc/src/javalin/src/main/java/JavalinHelloWorld.java deleted file mode 100644 index ae66d6fbf..000000000 --- a/lecture/misc/src/javalin/src/main/java/JavalinHelloWorld.java +++ /dev/null @@ -1,34 +0,0 @@ -import io.javalin.Javalin; -import io.javalin.http.Context; -import io.javalin.http.Handler; -import org.jetbrains.annotations.NotNull; - -/** Demo für Javalin: Hello World (MWE) */ -public class JavalinHelloWorld { - /** Starte die Demo */ - public static void main(String[] args) { - // Starte den Webserver auf Port 8080: "localhost:8080/" - Javalin app = Javalin.create().start(8080); - - // Registriere Handler für "localhost:8080/" - app.get( - "/", - new Handler() { - // Context ctx: see https://javalin.io/documentation#context - @Override - public void handle(@NotNull Context ctx) { - ctx.result("Hello World :-)"); - } - }); - - // Registriere Handler für "localhost:8080/exit" - app.get( - "/exit", - new Handler() { - @Override - public void handle(@NotNull Context ctx) { - System.exit(0); - } - }); - } -} diff --git a/lecture/misc/src/javalin/src/main/java/JavalinRandomNumber.java b/lecture/misc/src/javalin/src/main/java/JavalinRandomNumber.java deleted file mode 100644 index b55b079a7..000000000 --- a/lecture/misc/src/javalin/src/main/java/JavalinRandomNumber.java +++ /dev/null @@ -1,90 +0,0 @@ -import io.javalin.Javalin; -import io.javalin.http.Context; -import io.javalin.http.Handler; -import java.util.Random; -import java.util.logging.Logger; -import org.jetbrains.annotations.NotNull; - -/** Demo für Javalin: Erzeuge Zufallszahlen */ -public class JavalinRandomNumber { - static final Logger LOGGER = Logger.getLogger(JavalinRandomNumber.class.getName()); - - static final String HTML_BUTTONS = - "" - + "" - + ""; - - /** Starte die Demo */ - public static void main(String[] args) { - - // Starte den Webserver auf Port 8080: "localhost:8080/" - Javalin app = Javalin.create().start(8080); - - // Registriere Handler für "localhost:8080/" - handleRoot(app); - - // Registriere Handler "localhost:8080/int" - handleInt(app); - - // Registriere Handler für "localhost:8080/float" - handleFloat(app); - - // Registriere Handler für "localhost:8080/exit" - handleExit(app); - } - - private static void handleExit(Javalin app) { - app.get( - "/exit", - new Handler() { - @Override - public void handle(@NotNull Context ctx) { - LOGGER.info("Shut down!"); - System.exit(0); - } - }); - } - - private static void handleFloat(Javalin app) { - app.get( - "/float", - new Handler() { - @Override - public void handle(@NotNull Context ctx) { - ctx.html("

" + randomFloat() + "

" + HTML_BUTTONS); - } - }); - } - - private static void handleInt(Javalin app) { - app.get( - "/int", - new Handler() { - @Override - public void handle(@NotNull Context ctx) { - ctx.html("

" + randomInt() + "

" + HTML_BUTTONS); - } - }); - } - - private static void handleRoot(Javalin app) { - app.get( - "/", - new Handler() { - @Override - public void handle(@NotNull Context ctx) { - ctx.html("

Welcome!

" + HTML_BUTTONS); - } - }); - } - - private static String randomInt() { - Random r = new Random(); - return "" + r.nextInt(); - } - - private static String randomFloat() { - Random r = new Random(); - return "" + r.nextFloat(); - } -}