Eprobots JS Tutorial
Schritt für Schritt eine evolutionäre Robotik-Simulation aufbauen
Einleitung
In diesem Tutorial werde ich Schritt für Schritt eine Eprobots-Welt (Simulation genannt) aufbauen. Wir erschaffen die Simulation einer Welt mit künstlichen Lebewesen, die echte Evolution durchmachen und interessante Verhaltensweisen entwickeln.
Das Ganze kommt ohne externe Javascript-Bibliotheken und CSS aus. Es sind Grundkenntnisse in HTML und Javascript notwendig. Im Idealfall auch Erfahrungen mit Javascript-Klassen.
1 Grundgerüst / User-Interface
Wir fangen mit der index.html an. Alles was wir für das User-Interface brauchen ist ein Canvas zum Zeichnen der Welt und einen Button zum Starten und Stoppen der Simulation. Er bekommt die id btn_start_stop. Das Canvas-Element wird mit der Größe 800x400 vorbereitet. Die Größe kann hier verändert werden.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Eprobots JS Tutorial</title>
<meta name="description" content="Eprobots JS Tutorial">
</head>
<body>
<h1>Eprobots JS</h1>
<div>
<canvas id="canvas" width="800" height="400" style="background-color:#000000;"></canvas>
</div>
<div>
<button type="button" id="btn_start_stop">Start/Stopp</button>
</div>
<script src="main.js"></script>
</body>
</html>
main.js als Einstiegspunkt
Andererseits wird auch schon ein erstes Script namens main.js geladen. Dieses Script dient später auch als Einstiegspunkt für den Code und erledigt die notwendigen Initialisierungen.
main.js gibt vorerst nur einen Text aus.
console.log("Eprobots JS Tutorial - Start");
Abschluss
Öffnet man die index.html im Browser sollte man in der Javascript-Konsole die Ausgabe "Eprobots JS Tutorial - Start" erhalten.
2 Simulations-Klasse und Settings-Modul
Im zweiten Teil des Tutorials werden wir die erste Klasse erstellen und ein Modul einführen, dass wichtige Einstellungen aufnimmt.
Die Simulations-Klasse ist der Dreh- und Angelpunkt des Programms:
class Simulation {
constructor(canvas) {
console.log("Simulation class init")
let canvas_width = canvas.width;
let canvas_height = canvas.height;
console.log("Canvas: "+canvas_width+"x"+canvas_height);
this.world_width = canvas_width/settings.pixel_size+2;
this.world_height = canvas_height/settings.pixel_size+2;
console.log("world dimensions: "+this.world_width+"x"+this.world_height);
}
}
Im Konstruktor soll später unser Canvas-Objekt übergeben werden, damit die Simulation weiß, wohin gezeichnet werden soll. Als erstes werden dann die Dimensionen des Canvas in Pixeln ausgegeben.
Anschließend wird die interne Weltgröße ausgerechnet. Man kann die Welt auch genauso groß wie das Canvas machen, beispielsweise 800x400, aber dann werden wir unsere Objekte kaum sehen können. Daher ist es gerade für Übungszwecke besser, die einzelnen Objekte größer zu machen. Zum Beispiel 8x8 Pixel. Dieser Wert ist aber leicht veränderbar, da er in ein Modul namens settings.js ausgelagert wird. Die Anzahl der Objekte in der Breite wird dann mit canvas_width/settings.pixel_size berechnet. Analoges gilt für die Höhe.
Aber was ist mit diesem +2?
Es ist ein gerne angewandter Trick die "Spielfläche" um einen Rand von einer Zelle zu erweitern (zum Beispiel im Computerschach). Wollen wir beispielweise eine Welt von 10x10 Zellen haben, initialisieren wir die Welt intern mit einer Größe von 12x12 (jeweils eine weitere Zelle oben/unten und links/rechts). Sichtbar wird nur der innere Bereich sein, dass heißt nur dieser Bereich wird tatsächlich gezeichnet.
Dies vereinfacht die spätere "Spiellogik" enorm, da man dann nicht ständig abfragen muss, ob man sich noch innerhalb der Grenzen befindet. Dazu besetzt man später die Randzellen mit besonderen Barriere-Objekten. So reicht es dann aus zu prüfen, ob sich an der Zielstelle ein Barrieren-Objekt befindet oder nicht. Im Laufe des Tutorials sollte dies klarer werden.
Settings-Modul
An dieser Stelle bzw. in dieser Datei sollen später bequem und zentral (Simulations-)Einstellungen aufgenommen und verändern werden können. Alles was man gerne zentral als Parameter hätte sollte man dorthin auslagern, um keine Werte irgendwo im Code und vielleicht sogar mehrfach zu haben.
In Zukunft werden im Tutorial weitere Werte in die settings.js ausgelagert werden.
var settings = {
pixel_size: 8
}
Neue Dateien in index.html aufnehmen
Ganz wichtig: Die beiden neuen Dateien/Module müssen in der index.html aufgeführt werden. Dies muss bei allen neuen Dateien gemacht werden. Ich werde in den Codeblöcken immer vollständige Dateien zeigen. Neue Zeilen und diejenigen die verändert werden müssen, sind hervorgehoben.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Eprobots JS Tutorial</title>
<meta name="description" content="Eprobots JS Tutorial">
</head>
<body>
<h1>Eprobots JS</h1>
<div>
<canvas id="canvas" width="800" height="400" style="background-color:#000000;"></canvas>
</div>
<div>
<button type="button" id="btn_start_stop">Start/Stopp</button>
</div>
<script src="simulation.js"></script> ⬅️ NEU
<script src="settings.js"></script> ⬅️ NEU
<script src="main.js"></script>
</body>
</html>
Simulation-Instanz erstellen
Da jetzt die neuen Dateien erstellt und eingebunden sind, können wir in der main.js eine Simulations-Instanz erstellen. Dazu sollte die main.js an dieser Stelle ausnahmsweise komplett mit diesem Code ersetzt werden:
document.addEventListener("DOMContentLoaded", function() {
console.log("Eprobots JS Tutorial - Start");
let canvas = document.getElementById("canvas");
var simulation = new Simulation(canvas);
});
document.addEventListener("DOMContentLoaded", function() {}); sorgt dafür, dass der Code innerhalb der Funktion erst dann ausgeführt wird, wenn alles vollständig geladen wurde (im DOM).
Da die Simulation-Klasse in ihrem Konstruktor eine Canvas-Instanz benötigt, auf die gezeichnet werden soll, holen wir uns eine Referenz auf unser Canvas mit document.getElementById("canvas");. Der Zugriff erfolgt über das id-Attribut. Da unser Canvas schlicht "canvas" heißt, greifen wir auch so auf das Canvas-Element zu.
3 Welt initialisieren mit Terrain-Objekten
Als erstes müssen wir die Welt erzeugen. Dazu führen wir zwei neue Klassen ein: World und Terrain.
class World {
constructor(s, width, height) {
console.log("World constructor");
this.s = s;
// init
this.worldarr = new Array(width);
for (var x=0;x<this.worldarr.length;x++){
this.worldarr[x] = new Array(height);
for (var y=0;y<height;y++){
this.worldarr[x][y] = new Terrain(s, x, y);
}
}
}
}
📚 Vollständiges Tutorial
Vollständiges Tutorial öffnen →