Methoden Methoden anpassen und fehlende Funktionen hinzufügen

Ist es möglich irgendwie einzubauen, dass wenn der passedPoint hinter dem trainHead liegt eine Exception
Du musst wie @DagobertDuck mit dem Debugger herausfinden, wo das Problem liegt. So etwas ist sehr schnell gefunden: Breakpoint setzen, Schritt für Schritt durchgehen (Methoden erstmal mit Step-Over), Variablen mit den erwarteten Werten vergleichen. Wenn eine Methode was falsches zurückliefert, Breakpoint setzen, Spaß wiederholen, dann in die Methode per Step-In gehen usw.
Jap. Da findet man bugs, aber richtig eklige
 
Da ist man mal kurz nicht da....

Zu #297
Wenn allerdings track.getSwitchedTo() = (5,1) und point = (5,1) liefert, wird trotzdem in den Rumpf der Anweisung gegangen... Woran liegt das?
Die Variablen speichern Referenzen auf die Objekte. Mit "==" werden diese Referenzen verglichen, nicht die Objekte selbst. Daher vergleicht man Objekte mit equals.

Zu #300
Dein Code war ja wie folgt:
Ja, das Ergebnis ist doch ok, oder verstehe ich was falsch?
 
Da ist man mal kurz nicht da....

Zu #297

Die Variablen speichern Referenzen auf die Objekte. Mit "==" werden diese Referenzen verglichen, nicht die Objekte selbst. Daher vergleicht man Objekte mit equals.

Zu #300

Ja, das Ergebnis ist doch ok, oder verstehe ich was falsch?
Mit equals() bekomme ich dann allerdings eine NPE, wenn switchedTo = null ist (immer der Fall, wenn noch keine Grundeinstellung gewählt ist)

Zu #300

Das Ergebnis ist leider nicht in Ordnung. Ich lösche eine Track, sodass keine mehr existiert und dann kann ich nicht beliebig starten, da immer noch "Restpunkte" übrig sind, die nicht vollständig entfernt wurden (siehe Screenshot #300)
 
Mit equals() bekomme ich dann allerdings eine NPE, wenn switchedTo = null ist (immer der Fall, wenn noch keine Grundeinstellung gewählt ist)
Ja, das muss man halt abfangen.

Das Ergebnis ist leider nicht in Ordnung. Ich lösche eine Track, sodass keine mehr existiert und dann kann ich nicht beliebig starten, da immer noch "Restpunkte" übrig sind, die nicht vollständig entfernt wurden (siehe Screenshot #300)
Ach so, jetzt sehe ich das erst.

OK, dann räum hinterher noch auf: edges.entrySet().removeIf(e -> e.getValue().isEmpty());
 
Danke schön! Ich habe gerade noch einen Sonderfall bemerkt, der auch eine NPE erzeugt.
Code:
add switch (1,1) -> (5,1),(1,5)
put train 1 at (1,1) in direction 1,0 (Zug der Länge 3)
Exception in thread "main" java.lang.NullPointerException
    findTrack(...)
    putTrain(...)
    ...
Wenn also noch keine Weiche eingestellt ist und man den Zug irgendwo außer auf den Startpunkt setzt, wird eine saubere Fehlermeldung ausgegeben. Setzt man den Zug allerdings auf den Startpunkt, ergibt sich eine NPE. Wie und wo kann ich diesen Fall am besten abfangen?
 
In findTrack

Aber: grundsätzlich sollten die Methoden gar nicht erst aufgerufen werden, wenn die Weichen nicht gestellt sind, oder? Dann bietet sich eine Methode an, die das sicherstellt.
 
Ich bin gerade dabei den letzten Befehl zu implementieren.
Java:
    /*
    TODO:
        - Trains initially go in direction of the given direction vector and then follow the rail course.
        - Lets all trains drive by n x-units if the track is horizontal and by n y-units if it is vertical.
        - A crash takes place if two trains are located on the same track or position.
            - Start-/Endpoints always belong to the track that the rest of the train is located on.
        - If the track ends for a train, it will derail.
        - If two trains collide, both of them will derail.
        - Derailed trains will be taken off the track.
        - Print the position of all train heads (getHeadPoint)
        - If there is a crash in this process, the involved trains are printed. Trains that were not involved
          in it, continue driving.
     */
    public void step(short speed) {
          ...

    }

Hier habe ich mal die wichtigsten Anforderungen an den Befehl als TODO zusammengefasst. Könntest du bitte grob skizzieren, wie dies jetzt mit meiner bisherigen Modellierung am effizientesten umgesetzt werden kann? Das wäre super!
 
Ich würde nicht auf Effizienz gehen, sondern auf Einfachheit. Sprich: jeden Schritt einzeln durchführen, so dass diese Methode einfach zu
Java:
for (int i = 0; i < speed; i++) {
    singleStep();
}
wird.

singleStep: da ist die Herausforderung, die Bewegung inkl. der Übergänge zwischen den Tracks hinzubekommen. Das würde ich als erstes angehen.

Prinzipiell können nur Lok und der letzte Waggon von einem Übergang betroffen sein. Gibt es keinen Übergang, ist die Sache einfach: Neue Position des Zugs = aktuelle Position + Richtungsvektor des Zugs

Ist nur der letzte Waggon von einem Übergang betroffen, wäre das auch kein größeres Problem, denn wir hätten ja requiredTracks...

Bleibt die Frage nach dem Übergang. Steht der Zug vor dem step auf einem Endpunkt und fährt er entgegen der Richtung des anderen Endpunkts, dann muss ein Übergang erfolgen. D. h. aus edges.get(point) muss der neue Track ausgewählt werden und der Richtungsvektor des Zugs muss eingestellt werden.

D. h. für einen Zug:
Code:
Sei akt := der Track, auf dem die Lok steht
Sei rAkt := die vom Zug belegten Tracks
Wenn Zug auf Endpunkt steht und Zug in Fahrtrichtung über das Gleis hinaus fahren würde, dann 
   neu := Track, der mit akt verbunden ist
   setze Richtungsvektor des Zugs in Abhängigkeit von neu 
Ende Wenn
Ändere Position des Zugs um den Richtungsvektor
rNeu := requiredTracks 
Entferne Zug von jedem Track, der in rAkt vorhanden, in rNeu nicht vorhanden ist.
Setze Zug auf jeden Track, der in rNeu vorhanden, in rAkt nicht vorhanden ist.

Könnte in etwa hinkommen.
 
Danke! Dann werde ich mich morgen mal daran versuchen. Kurze Frage:
Java:
    return p1.x == p.x && p.x == p2.x && (p.y-p1.y)/(p2.y-p1.y) == 0 && (p2.y-p1.y)/(p.y-p1.y) > 0 ||
        p1.y == p.y && p.y == p2.y && (p.x-p1.x)/(p2.x-p1.x) == 0 && (p2.x-p1.x)/(p.x-p1.x) > 0;
Den Code hast du mal für die canNavigateTo() Methode geschrieben. Hast du Namensideen für die einzelnen boolean, sodass ich die Werte auslagern kann? Ansonsten ist das so unübersichtlich. Allerdings fallen mir keine passenden Namen ein, die nicht viel zu lang sind. Dazu kommt noch, dass x und y bei mir getFirstComponent bzw. getSecondComponent heißen. Laut Checkstyle ist x und y als Variablensbenennung schlechter Stil.
 
Du kannst den Spaß natürlich in eine Methode in Point klatschen, dann musst Du natürlich p.x/p.y durch x/y ersetzen. Methode boolean isOnLine(Point p1, Point p2) (oder isBetween, wobei between...)

Und wenn es Dir gefällt, kannst Du das natürlich auch mit ifs aufteilen.
 
Ich möchte niemanden auf die Schuhe pinkeln
Du pinkelst doch niemandem auf die Schuhe, wenn Du Fehler entdeckst. Du müsstest allerdings schon genau angeben, wo das Problem liegt, für welche Koordinaten Du welchen Wert erwartest und was stattdessen zurückgegeben wird.

Der return alleine funktioniert in wenigstens einem Fall nicht: entspricht der zu prüfende Punkt dem Startpunkt, kommt es (ohne weitere Prüfungen) z. B. zu einer ArithmeticException.
 
Wobei mir gerade auffällt, dass der return auch unnötig kompliziert ist:
Java:
return p1.x == x && x == p2.x && Math.min(p1.y, p2.y) <= y && y <= Math.max(p1.y, p2.y) ||
    p1.y == y && y == p2.y && Math.min(p1.x, p2.x) <= x && x <= Math.max(p1.x, p2.x);
 
@mihe7 Mir kommt folgender Code zu lang für das was er tut vor. Hast du eine Idee, wie er mit Hilfe von der Stream API verkürzt werden kann, obwohl vorher noch nicht klar ist, wie viele Endpunkte existieren?
Java:
        if (track.getPoints().size() == 2) {
            if (track.getStartPoint().equals(track.getEndPoints().get(0))) {
                throw new ...("startpoint cannot be equal to an endpoint");
            }
        } else if (track.getPoints().size() == 3) {
            if (track.getStartPoint().equals(track.getEndPoints().get(0)) || track.getStartPoint().equals(track.getEndPoints().get(1))) {
                throw new ...("startpoint cannot be equal to an endpoint");
            }
        }
 
@mihe7 Mir kommt folgender Code zu lang für das was er tut vor. Hast du eine Idee, wie er mit Hilfe von der Stream API verkürzt werden kann, obwohl vorher noch nicht klar ist, wie viele Endpunkte existieren?
Java:
        if (track.getPoints().size() == 2) {
            if (track.getStartPoint().equals(track.getEndPoints().get(0))) {
                throw new ...("startpoint cannot be equal to an endpoint");
            }
        } else if (track.getPoints().size() == 3) {
            if (track.getStartPoint().equals(track.getEndPoints().get(0)) || track.getStartPoint().equals(track.getEndPoints().get(1))) {
                throw new ...("startpoint cannot be equal to an endpoint");
            }
        }
Bei solchen Dingen musst Du Dir überlegen, was die Frage ist: gibt es einen Endpunkt, der mit dem Startpunkt übereinstimmt? Das lässt sich fast 1:1 übersetzen:
Java:
if (track.getEndPoints().stream().anyMatch(p -> p.equals(track.getStartPoint())) {
    throw new ...
}
 
Spitze, vielen Dank!

Bei der singleStep Methode bin ich leider etwas überfragt...
Java:
    private void singleStep() {
        for (Track track : tracks.values()) {
            for (Train train : trainsOnTrack) {
                if (track.getCurrentTrain() != null) {
                    Track akt = track.getCurrentTrain()...;
                    List<Track> rAkt = getRequiredTracks(//Wie soll ich hier die Argumente für finden?);
                    if () {
                        Track neu = getConnection(...);
                       
                    }
                }
                ...
            }
        }
    }
 
Kurze Frage zu einer etwas älteren Methode: Wieso hat die isConnectedAfterRemoving() Methode eigentlich ein Set von Kanten als Parameter und nicht einfach nur eine einzige Kante? Wenn also eine beliebige Kante des angegebenen Sets bei seiner Entfernung zu zwei Teilgraphen führen würde, wird false zurückgegeben, richtig?
 
Ich habe daran gedacht, dass alle Züge mit null initialisiert werden, was das momentane Gleis betrifft. Folglich kann man in einer for-Schleife über die Liste aller Züge iterieren und damit dann letztendlich alle "rausfiltern", welche auf den Gleisen stehen und welche nicht...
Und dann stehe ich vor dem gleichen Problem wie bei dir
 
Kurze Frage zu einer etwas älteren Methode: Wieso hat die isConnectedAfterRemoving() Methode eigentlich ein Set von Kanten als Parameter und nicht einfach nur eine einzige Kante? Wenn also eine beliebige Kante des angegebenen Sets bei seiner Entfernung zu zwei Teilgraphen führen würde, wird false zurückgegeben, richtig?
Weil man auf diese Weise dann zwei Gleise (Weichen) und normale Weichen auf einen Schlag entfernen kann
 
Ist es nicht ineffizient noch eine Liste an Trains anzulegen, welche sich nur auf den Gleisen befinden?
So hat man dann einige Listen und das würde dann nicht mehr wirklich der OO entsprechen.
Ineffizient ist, zwei Schleifen zu brauchen. Mit "Anti-OO" haben mehrere Listen aber nichts zu tun.

Es gibt eine 1:n-Beziehung zwischen Train und Track, d. h. ein Zug kann mehrere Tracks belegen, während sich auf einem Track immer nur ein Zug befinden kann.

Nun stellt sich die Frage der benötigten Richtung dieser Beziehung: Train -> Track, Track -> Train oder Train <-> Track?

Bislang gab es die Anforderung, dass das Netz die Anfrage beantworten können muss, ob ein gegebener Track durch einen Zug belegt wird, um z. B. Kollisionen zu erkennen. Das haben wir implementiert, indem wir uns in Track einfach merken, welcher Zug ihn belegt.

Jetzt haben wir eine neue Anforderung: welche Tracks werden in welcher Reihenfolge von einem Zug belegt? Konzeptionell haben wir also eine bidirektionale Beziehung. Die umkehrte Richtung lässt sich nun verschiedenartig realisieren:

Möglichkeit 1: wir setzen Track -> Train tatsächlich als bidirektionale Beziehung um und verwalten in Train eine Liste der belegten Tracks. Dann kann bei putTrain z. B. train.assignTracks(required) aufgerufen werden. Train speichert die Liste und setzt sich selbst auf die Tracks.

Möglichkeit 2: wir suchen. Wir können den Track der Lok ermitteln und wir kennen die Richtung der Lok, daher können wir auch die erforderlichen Tracks ermitteln.

Natürlich gibt es noch andere Möglichkeiten. Wenn man z. B. die Beziehung außerhalb von Train und Track abbilden möchte, kann man dies mit z. B. Map<Train,List<Track>> und Map<Track, Train> erledigen.
 
Die erforderlichen Tracks erhält man dann über getRequiredTracks().
Der Startpunkt ist gegeben, die step-Geschwindigkeit und der Richtungsvektor auch. Daher muss man nun eine Methode implementieren, die den nächsten point ermittelt, welcher z.B. nach dem step-Befehl in Richtung des Richtungsvektor kommen würde.
So ist nun weiter vorzugehen, oder nicht?
 
Spitze, vielen Dank!

Bei der singleStep Methode bin ich leider etwas überfragt...
Java:
    private void singleStep() {
        for (Track track : tracks.values()) {
            for (Train train : trainsOnTrack) {
                if (track.getCurrentTrain() != null) {
                    Track akt = track.getCurrentTrain()...;
                    List<Track> rAkt = getRequiredTracks(//Wie soll ich hier die Argumente für finden?);
                    if () {
                        Track neu = getConnection(...);
                     
                    }
                }
                ...
            }
        }
    }
Könntest du mir hier bitte noch etwas den Weg weisen mit Hilfe von detaillierten Pseudo-Code?
 
Könntest du mir hier bitte noch etwas den Weg weisen mit Hilfe von detaillierten Pseudo-Code?
Noch detaillierter?!?

Wie @Mika34 schreibt:
Die erforderlichen Tracks erhält man dann über getRequiredTracks().
Der Startpunkt ist gegeben, die step-Geschwindigkeit und der Richtungsvektor auch.

Das führt dann zu einer Methode wie (ich habe sie mal nicht getRequiredTracks genannt, damit wir hier im Forum wissen, was gemeint ist):
Java:
public List<Track> getTracksByTrain(Train train) { 
    Point point = train.getPosition();
    Track track = findTrack(point);
    return getRequiredTracks(track, point, train.getDirection(), train.getLength());
}

Java:
for (Train train : trains) {
    singleStep(train, getTracksByTrain(train));
}
mit
Java:
public void singleStep(Train train, List<Track> assignedTracks) {
    if (assignedTracks.isEmpty()) { return; }

    // ... weiter im Text    
}


Daher muss man nun eine Methode implementieren, die den nächsten point ermittelt, welcher z.B. nach dem step-Befehl in Richtung des Richtungsvektor kommen würde.
So ist nun weiter vorzugehen, oder nicht?
Das könntest Du machen, ist aber kompliziert: wenn die Schrittweise groß genug ist, werden Gleise und Kollisionen ggf. übersprungen. Daher der Vorschlag, jeden einzelnen Schritt zu simulieren. Das ist zwar zeitlich aufwändiger, aber verhältnismäßig einfach und robust.
 
Daher der Vorschlag, jeden einzelnen Schritt zu simulieren
Aber um jeden Schritt einzeln zu simulieren benötigt man dennoch eine Methode, welche dann so aussieht:
Code:
public Point getNextPositionOfTrain(Point normedDirectionVector){
//Gibt den nächsten Punkt zurück, welcher sich in Bewegungsrichtung befindet
}

-----
    |
    -------
Denn wenn das Gleisnetz um Kurven geht, dann soll der nächste Punkt genau bestimmt sein (wie im Bsp.). Ist dieser Ansatz richtig?
Wenn ja, lässt sich dann eine unserer bisherigen Methoden anpassen und auf diesen Fall dann anwenden?
 
Noch detaillierter?!?

Wie @Mika34 schreibt:


Das führt dann zu einer Methode wie (ich habe sie mal nicht getRequiredTracks genannt, damit wir hier im Forum wissen, was gemeint ist):
Java:
public List<Track> getTracksByTrain(Train train) {
    Point point = train.getPosition();
    Track track = findTrack(point);
    return getRequiredTracks(track, point, train.getDirection(), train.getLength());
}

Java:
for (Train train : trains) {
    singleStep(train, getTracksByTrain(train));
}
mit
Java:
public void singleStep(Train train, List<Track> assignedTracks) {
    if (assignedTracks.isEmpty()) { return; }

    // ... weiter im Text  
}



Das könntest Du machen, ist aber kompliziert: wenn die Schrittweise groß genug ist, werden Gleise und Kollisionen ggf. übersprungen. Daher der Vorschlag, jeden einzelnen Schritt zu simulieren. Das ist zwar zeitlich aufwändiger, aber verhältnismäßig einfach und robust.
Wie sieht denn die getPosition() Methode aus? Momentan wird in Train ja noch nichts über Tracks gespeichert. Muss also doch wie Mika gesagt hat, eine extra Liste in der Klasse Train angelegt werden...?
 
Denn wenn das Gleisnetz um Kurven geht, dann soll der nächste Punkt genau bestimmt sein (wie im Bsp.). Ist dieser Ansatz richtig?
Richtig.

Wenn ja, lässt sich dann eine unserer bisherigen Methoden anpassen und auf diesen Fall dann anwenden?
s. Algorithmus

Wie sieht denn die getPosition() Methode aus? Momentan wird in Train ja noch nichts über Tracks gespeichert
Die Position der Lok muss im Train gespeichert werden.
 
Sei akt := der Track, auf dem die Lok steht Sei rAkt := die vom Zug belegten Tracks Wenn Zug auf Endpunkt steht und Zug in Fahrtrichtung über das Gleis hinaus fahren würde, dann neu := Track, der mit akt verbunden ist setze Richtungsvektor des Zugs in Abhängigkeit von neu Ende Wenn Ändere Position des Zugs um den Richtungsvektor rNeu := requiredTracks Entferne Zug von jedem Track, der in rAkt vorhanden, in rNeu nicht vorhanden ist. Setze Zug auf jeden Track, der in rNeu vorhanden, in rAkt nicht vorhanden ist.
Was bedeutet in deinem Fall hier "Ende Wenn"? Bedeutet das, dass hier der obere Teil mit einem break fertig ist? Denn dann verstehe ich nicht was es mit dem Wenn auf sich hat, weil da keine Bedingung folgt
 
Was bedeutet in deinem Fall hier "Ende Wenn"?
Das kennzeichnet einfach nur das Ende des Wenn-Blocks.
Code:
if (Zug auf Endpunkt steht und Zug in Fahrtrichtung über das Gleis hinaus fahren würde) {
   neu := Track, der mit akt verbunden ist
   setze Richtungsvektor des Zugs in Abhängigkeit von neu 
}
 
Ich werde aus deinem Code einfach nicht schlauer.
Das Grundprinzip ist doch, wir wollen als aller erstes nun für den singleStep herausfinden was nun der nächste Punkt ist, ausgehend von dem Startpunkt, auf welchem sich der Kopf des Zuges befindet.
Liege ich damit richtig
 
Soll man nun die gesamte singleStep Methode als Gesamtheit in eine Methode schreiben oder doch lieber aufsplitten?
Denn nach deiner Skizze benötigt man eigentlich keine Hilfsmethoden mehr als wir schon haben, wie man sie beispielsweise bei dem putTrain() implementieren musste
 
Ich werde aus deinem Code einfach nicht schlauer.
Der Algorithmus ist auch nicht vollständig, das war eine erste, grobe Überlegung.

Das Grundprinzip ist doch, wir wollen als aller erstes nun für den singleStep herausfinden was nun der nächste Punkt ist, ausgehend von dem Startpunkt, auf welchem sich der Kopf des Zuges befindet.
Liege ich damit richtig
Exakt. Wie Du das anstellst, bleibt Dir überlassen.

Ich habe mir halt überlegt, dass
a) das die neue Position der Lok leicht zu berechnen ist, wenn kein Gleiswechsel notwendig ist
b) ein Gleiswechsel nur auftreten kann, wenn die Lok an den Endpunkten eines Tracks steht.

Wenn ein Gleiswechsel notwendig ist (Fall b), ändert sich ggf. die Richtung des Zugs. Danach kann man verfahren wie bei a) und erhält so die neue Position der Lok.
 
@mihe7 Wie kocht man Nudeln??
Code:
Sei g die Menge der Nudeln in Gramm.
z := angegebene Kochzeit
s := g / 10
w := g * 10
t := wähleTopf(w)
Fülle w Gramm Wasser in t
Füge s Gramm Salz hinzu
Stelle t auf eine Herdplatte p
l := 100%
setzeLeistung(p, l)
sprudeln := false
so lange not sprudeln, wiederhole {
    sprudeln := sprudeltWasserIn(t)
}
l := l * 0.5
setzeLeistung(p, l)
Füge g Gramm Nudeln zu t hinzu
ez := aktuelleZeit() + z
so lange aktuelleZeit() < ez, wiederhole {
    if (!simmertWasserIn(t)) {
        l = l+0.1;
        setzeLeistung(p, l);
    } else if (kochtUeber(t)) {
        rufe(Frau);
    } else if (sprudeltWasserIn(t)) {
        l = l * 0.5;
        setzeLeistung(p, l);
    }
    schlafeSekunden(30)
}
setzeLeistung(p, 0)
n := entferneNudeln(t)

EDIT: Korrektur - kochtUeber muss vor sprudeltInWasser geprüft werden.
 
setze Richtungsvektor des Zugs in Abhängigkeit von neu
So, ich komme weiter. Jedoch verstehe ich noch nicht wie ich den alten Richtungsvektor nun in Abhängigkeit von dem verbundenen Gleis setzen soll. Das Normieren von diesem Vektor ist klar... Jedoch raffe ich nicht inwiefern man durch die gegebenen Informationen einen neuen Richtungsvektor "konstruiert". Hast du da einen Vorschlag?
Ich habe an den Vergleich von yCoordDirection und yCoordNewTrack gedacht und wenn yCoordNewTrack > yCoordDirection, dann ist newDir=(0,1) und umgekehrt (0,-1), aber das ist nicht universell einsetzbar...
 

Zurück
Oben