Java Gute Umsetzung eines Textbasieren Spiels?

C

Crushkit

Gast
Hallo,

ich bin grade dabei, mir die Objektorientierte Programmierung anzueignen und dafür mache ich grade (unter anderem) ein Textbasieretes Spiel. Nur bin ich mir jetzt nicht sicher, ob ich die Sachen gut umgesetzt habe und ob das so auch ein guter Ansatz ist oder nicht.

Naja, hier erst einmal ein Teil, bei dem ich mir absolut nicht sicher bin, ob das so gut ist:
Das hier ist dann ein allgemeingültiger Befehl für das Spiel:
Code:
public class Befehl
{
    private String zweitesWort;
    
    public Befehl()
    {
        zweitesWort = null;
    }
    
    public String gibZweitesWort()
    {
        return zweitesWort;
    }
    
    public void setZweitesWort(String zweitesWort)
    {
        this.zweitesWort = zweitesWort;
    }

    public boolean hatZweitesWort()
    {
        return (zweitesWort != null);
    }
}

und das hier soll dann ein Befehl sein, der nur im Kampf gültig ist.
Code:
public class KampfBefehl extends Befehl
{
    public KampfBefehl()
    {
        super();
    }
}

Hier werden die ie ganzen Befehle initialisiert und erzeugt. Zudem noch eine Methode, um Sie alle auszugeben und um
sich einen Befehl aus der HashMap zu holen.

Code:
public class Befehlswoerter
{
    private HashMap<String, Befehl> befehle;

    public Befehlswoerter()
    {
        befehle = new HashMap<String, Befehl>();
        
        befehle.put("gehe", new Befehl());
        befehle.put("aufheben", new Befehl());
        befehle.put("angriff", new Befehl());
        befehle.put("weiter", new KampfBefehl());
        befehle.put("fliehen", new KampfBefehl());
        befehle.put("ende", new Befehl());
    }

    public Befehl get(String wort)
    {
        return (Befehl)befehle.get(wort);
    }
   
    public void alleAusgeben() 
    {
        for(Iterator i = befehle.keySet().iterator(); i.hasNext(); ) 
        {
            System.out.print(i.next() + "  ");
        }
        System.out.println();
    }
}

Das hier ist der Parser um die Benutzereingaben zu validieren und dann einen Befehlsobjekt zu erstellen.
Code:
public class Parser 
{
    private Scanner leser;  
    private Befehlswoerter befehle; 
    
    private String wort1 = null;

    public Parser() 
    {
        befehle = new Befehlswoerter();
        leser = new Scanner(System.in);
    }

    public Befehl liefereBefehl() 
    {
        String eingabezeile;
        String wort2 = null;

        System.out.print("\n> ");
        
        eingabezeile = leser.nextLine();   
        // Entfernen von leerzeichen am ende und anfang und zu kleinschreibung konvertieren
        eingabezeile = eingabezeile.trim().toLowerCase();
        
        System.out.println(eingabezeile);
        
        Scanner zerleger = new Scanner(eingabezeile); // Suche und finde zwei Eingabewörter
        if(zerleger.hasNext()) 
        {  
            wort1 = zerleger.next(); // erstes Wort lesen
                
            if (zerleger.hasNext()) 
                wort2 = zerleger.next(); // zweites Wort lesen
        }
        
        // Pruefen, ob der Befehl bekannt ist.
        Befehl befehl = befehle.get(wort1);
        if(befehl != null) 
            befehl.setZweitesWort(wort2);
            
        return befehl;
    }

    public void zeigeBefehle()
    {
        befehle.alleAusgeben();
    }
    
    public String gibBefehlswort() 
    {
        return wort1;
    }
}

Erst einmal dazu was: Ist das so ein guter Weg? Ich habe ja jetzt einen KampfBefehl von Befehl abgeleitet. Diese sollen dann nur im Kampf gültig sein.

Das Programm läuft bei mir solange in einer Schleife, bis es beendet wird:
Code:
boolean beendet = false;
while(!beendet) 
{
    Befehl befehl = parser.liefereBefehl();         
    if(befehl == null) 
    {
        System.out.println("\nDiesen Befehl gibt es nicht.");
    }
    else 
    {
       String befehlswort = parser.gibBefehlswort();
       beendet = verarbeiteBefehl(befehlswort, befehl);
    }

    if(!spieler.istAmLeben())
    {
        verloren();
        beendet = true;
    }
}

Naja, da hole ich mir dann ja von dem Parser die Benutzereingabe und das Befehlswort, also gehe oder hilfe oder ein sonstiger gültiger Befehl und dann rufe ich die vearbeiteBefehl Methode auf und übergebe dieser das Befehlswort und das Befehlsobjekt.
Code:
private boolean verarbeiteBefehl(String befehlswort, Befehl befehl) 
{
    boolean beenden = false;

    if(befehlswort.equals("gehe")) 
    {
        wechsleRaum(befehl);
    }
    else if(befehlswort.equals("aufheben")) 
    {
        gegenstandNehmen(befehl);
    }
    else if(befehlswort.equals("angriff"))
    {
        kampf(befehl);
    }
    else if(befehlswort.equals("ende"))
    {
        beenden = beenden(befehl);
    }
    
    return beenden;
}

Aber ist das so wohl ein guter Ansatz? Die verarbeite Befehl Methode befindet sich wie auch die Hauptschleife in einer Klasse namens "Spiel".

Das ist dann bspw. die Methode um den Raum zu wechseln:
Code:
private void wechsleRaum(Befehl befehl) 
{
    Raum aktuellerRaum = spieler.getAktuellerRaum();
    
    if(!befehl.hatZweitesWort()) 
    {
        System.out.println("\nWohin moechten Sie gehen?");
        return;
    }

    String richtung = befehl.gibZweitesWort();
    Raum naechsterRaum = aktuellerRaum.gibAusgang(richtung); // Versuchen, raum zu verlassen

    if(naechsterRaum == null) 
    {
        System.out.println("\nIn dieser Richtung befindet sich kein Raum!");
    }
    else if(spieler.weitergehen(richtung)) 
    {
        System.out.println(spieler.gibLangeBeschreibung());
        
        if(naechsterRaum == raumVorAusgang)
        {
            System.out.println("\nSie werden von einem großen Troll angegriffen!");
            boss.greifeAn(spieler);
        }
    }
}

Da ist auch eines meiner Probleme, also da wo ich mich frage, ob ich das gut umgesetzt habe. Ich überprüfe ja in dieser wechselRaum Methode, ob es sich bei dem Raum um den Raum vor dem Ausgang handelt aber ist das so ein guter Weg oder gehört das da nicht hin?

und hier auch noch eines meiner ganz großen Probleme. Es geht dabei um einen Kampf zwischen einem Spieler und einem Monster. Bräuchte ich eigentlich für diesen Kampf noch eine Klasse oder kann man das so laufen lassen?
So sieht der kampf derzeit bei mir aus. Der befindet sich komplett in der Spielklasse:

Code:
private void kampf(Befehl befehl)
{
    boolean fliehen = false;
    
    if(befehl.hatZweitesWort()) 
    {
        System.out.println("Das ist leider nicht möglich! Sie dürfen nur 'angriff' eingeben!");
        return;
    }
                   
    Monster monster = spieler.getAktuellerRaum().getMonster(); 
    
    spieler.greifeAn(monster);
    monster.greifeAn(spieler); 
    System.out.println("\nWeiterkämpfen? Dann geben Sie bitte 'weiter' ein.");
        
    // Solange bis nur noch ein Lebewesen lebt. Der Kampf wird auch beendet, falls der Spieler fliehen konnte
    while((!spieler.istTot() && !monster.istTot()) && !fliehen)
    {         
        Befehl kampfBefehl = parser.liefereBefehl();  
        String befehlswort = parser.gibBefehlswort();
        
        System.out.println(kampfBefehl);
        System.out.println(befehlswort);
        
        if(kampfBefehl == null) 
        {
            System.out.println("\nDiesen Befehl gibt es nicht!");
        }
        else if(kampfBefehl.getClass() != KampfBefehl.class)
        {
            System.out.println("\nDiesen Befehl dürfen Sie im Kampf nicht nutzen! weiter und fliehen sind gültige Befehle.");
        }
        else
        {          
            if(befehlswort.equals("weiter")) 
            {
                spieler.greifeAn(monster);
                monster.greifeAn(spieler);     
                
                if(!spieler.istTot() && !monster.istTot())
                    System.out.println("\nWeiterkämpfen? Dann geben Sie bitte 'weiter' ein.");
            }
            else if(befehlswort.equals("fliehen"))
            {                
                if(monster.kannSpielerVerfolgen())
                {
                    System.out.println("Sie konnten leider nicht entkommen und das Monster hat Sie angegriffen!");
                    monster.greifeAn(spieler);
                    System.out.println("\nWas möchten Sie machen? Weiterkämpfen oder noch einmal versuchen zu fliehen?");
                }
                else
                {
                    fliehen = true;
                    String zufaelligeRichtung = spieler.getAktuellerRaum().setZufaelligerFluchtraum(spieler.getAktuellerRaum());
                    spieler.weitergehen(zufaelligeRichtung);
                    
                    System.out.println("Sie konnten dem Monster in Richtung " + zufaelligeRichtung + " entkommen!");
                    System.out.println(spieler.getAktuellerRaum().gibLangeBeschreibung());
                }                
            }       
        }   
    }
    
    if (!spieler.istTot() && !fliehen)
        System.out.println("Sie haben das Monster besiegt!"); 
}

Gehört das da hin oder sollte man da dann eine Klasse Kampf oder so ähnlich erstellen und da das ganze laufen lassen aber wie baue ich das mit in die Spielklasse ein?
Soll ich da dann bei der vearbeiteBefehl Methode die Kampf Methode aufrufen und dann ein Kampf-Objekt erstellen und mir das Monster des aktuellen Raums holen und den Spieler und dann so ein Kampf Objekt erstellen.
Code:
public class Kampf
{
    private Spieler spieler;
    private Monster monster;
    
    public Kampf(Spieler spieler, Monster monster) 
    {
        this.spieler = spieler;
        this.monster = monster;      
    }
    
    public void kampf(Befehl befehl)
    {    
        boolean fliehen = false;
                       
        spieler.greifeAn(monster);
        monster.greifeAn(spieler); 
        System.out.println("\nWeiterkämpfen? Dann geben Sie bitte 'weiter' ein.");
            
        // Solange bis nur noch ein Lebewesen lebt. Der Kampf wird auch beendet, falls der Spieler fliehen konnte
        while((!spieler.istTot() && !monster.istTot()) && !fliehen)
        {         
            Befehl kampfBefehl = parser.liefereBefehl();  
            String befehlswort = parser.gibBefehlswort();
            
            System.out.println(kampfBefehl);
            System.out.println(befehlswort);
            
            if(kampfBefehl == null) 
            {
                System.out.println("\nDiesen Befehl gibt es nicht!");
            }
            else if(kampfBefehl.getClass() != KampfBefehl.class)
            {
                System.out.println("\nDiesen Befehl dürfen Sie im Kampf nicht nutzen! weiter und fliehen sind gültige Befehle.");
            }
            else
            {          
                if(befehlswort.equals("weiter")) 
                {
                    spieler.greifeAn(monster);
                    monster.greifeAn(spieler);     
                    
                    if(!spieler.istTot() && !monster.istTot())
                        System.out.println("\nWeiterkämpfen? Dann geben Sie bitte 'weiter' ein.");
                }
                else if(befehlswort.equals("fliehen"))
                {                
                    if(monster.kannSpielerVerfolgen())
                    {
                        System.out.println("Sie konnten leider nicht entkommen und das Monster hat Sie angegriffen!");
                        monster.greifeAn(spieler);
                        System.out.println("\nWas möchten Sie machen? Weiterkämpfen oder noch einmal versuchen zu fliehen?");
                    }
                    else
                    {
                        fliehen = true;
                        String zufaelligeRichtung = spieler.getAktuellerRaum().setZufaelligerFluchtraum(spieler.getAktuellerRaum());
                        spieler.weitergehen(zufaelligeRichtung);
                        
                        System.out.println("Sie konnten dem Monster in Richtung " + zufaelligeRichtung + " entkommen!");
                        System.out.println(spieler.getAktuellerRaum().gibLangeBeschreibung());
                    }                
                }       
            }   
        }
        
        if (!spieler.istTot() && !fliehen)
            System.out.println("Sie haben das Monster besiegt!"); 
    }
}

Nur ist dann ja das Problem, das ich eigentlich noch den Befehl brauche oder sollte ich dann noch irgendwie weiter seperieren? Mit dem Teil komme ich grade aktuell einfach nicht weiter.

Zudem sollen diese zwei Befehle weiter und fliehen ja die einzigen gültigen Befehle im kampf sein aber wenn ich oben bei der Methode wechsleRaum in diesem Raum vor dem Ausgang bin, soll sich der Spieler auch schon im Kampf befinden aber wie sorge ich dafür, das der Spieler dann nur diese zwei Befehle nutzen kann, also nur KampfBefehl Objekte?

Ist das so derzeit ein guter Aufbau oder eher nicht? Ich glaube, das da einiges nicht an der richtigen Stelle ist aber ich bin auch nicht so lange in der Objektorientierung drin.
 
Zuletzt bearbeitet von einem Moderator:
Hallo,

um den gesamten Code zu lesen, fehlt mir ehrlich gesagt die Zeit. Das Grundproblem lässt sich vergleichsweise leicht durch eine Zustandsmaschine lösen. Du definiert über ein Enum die Menge an Zuständen, etwa "im Kampf" und "normal". Die Zustände definieren wiederum, welche Aktionen (Befehle) gerade gültig sind. Das kannst du entweder über if-else-Wüsten abbilden, oder die Befehle wieder in einem eigenen Enum definieren, und im Konstruktor der Zustands-Enums gibts du die dafür gültigen Befehle ein.

Das ist aber nur eine Lösung. Generell gibt es hierbei oft kein "richtig" oder "falsch", höchstens ein "besser" und "schlechter". Oft genug hängt auch das wieder von persönlichen Vorlieben und Coding Style ab. Zumindest so lange eine Lösung korrekte Ergebnisse liefert.

Gruß,
exr
 
Erweiternd zum enum Vorschlag von exr wuerde ich vorschlagen ein Interface "Befehlsverarbeiter" mit der Funktion verarbeiteBefehl(Befehl befehl) zu erstellen (oder eine Klasse von der du diese Funktion vererbst). Dann koenntest du zwei Klassen "Erkunden" und "Kaempfen" erstellen, welche dieses Interface implementieren. Dann je nach Zustand (aus dem enum definiert) kannst du den Befehl dann an das jeweils passende Objekt uebergeben.

Kann etwas "kniffelig" werden, wenn du dann Variablen veraendern willst, die nicht im scope der jeweiligen klasse sind. Aber da gibt es auch genuegend Loesungswege (Parameter, Anonymous class, return value, callback, ... ).
 
Zuletzt bearbeitet:
Es wäre auch schön, wenn jemand oder ihr Bezug auf meinen Ansatz nehmen könntet und mir sagen könntet, warum ich das nicht so machen soll. Ist mein Weg mit der Unterklasse von Befehl schlecht und wenn ja, warum?

Waum der Weg über die enum? Was bietet das für Vorteile oder warum generell über die enum? Dann bräuchte ich ja meine Unterklasse KampfBefehl nicht mehr?

Wie würde das denn dann mit der enum aussehen? Das die enum so oder ähnlich aussehen könnte ist schon klar.
Code:
public enum Zustaende
{
    NORMAL, KAMPF, UNKNOWN;
}

Aber wo gehört das dann mit dem Überprüfen des Zustandes hin, also in welche Klasse?

Und wie sieht es mit der kampf Methode (oder der Klasse aus)?
Ergänzung ()

Ich habe auch die Kampf Klasse noch einmal ein wenig überarbeitet. Ich denke, das es so besser ist.

Code:
public class Kampf
{
    private Spieler spieler;
    private Monster monster;
    
    private Parser parser;
    
    private boolean konnteFliehen;
    
    public Kampf(Spieler spieler, Monster monster) 
    {
        this.spieler = spieler;
        this.monster = monster;      
        
        parser = new Parser();   
        konnteFliehen  = false;
    }
    
    private void eineKampfRunde()
    {    
        spieler.greifeAn(monster);
        monster.greifeAn(spieler);             
    }

    
    private void entkamMonster()
    {
        String zufaelligeRichtung = spieler.getAktuellerRaum().setZufaelligerFluchtraum(spieler.getAktuellerRaum());
        spieler.weitergehen(zufaelligeRichtung);
        
        System.out.println("Sie konnten dem Monster in Richtung " + zufaelligeRichtung + " entkommen!");
        System.out.println(spieler.getAktuellerRaum().gibLangeBeschreibung());
    }
    
    public void kampf()
    {
        // Solange bis nur noch ein Lebewesen lebt. Der Kampf wird auch beendet, falls der Spieler fliehen konnte
        do
        {         
            Befehl kampfBefehl = parser.liefereBefehl();  
            String befehlswort = parser.gibBefehlswort();
            
            if(kampfBefehl == null) 
            {
                System.out.println("\nDiesen Befehl gibt es nicht!");
            }
            else if(kampfBefehl.getClass() != KampfBefehl.class)
            {
                System.out.println("\nDiesen Befehl dürfen Sie im Kampf nicht nutzen! weiter und fliehen sind gültige Befehle.");
            }
            else
            {          
                if(befehlswort.equals("weiter")) 
                {
                    eineKampfRunde();     
                    
                    if(!spieler.istTot() && !monster.istTot())
                        System.out.println("\nWeiterkämpfen? Dann geben Sie bitte 'weiter' ein.");
                }
                else if(befehlswort.equals("fliehen"))
                {                
                    if(monster.kannSpielerVerfolgen())
                    {
                        System.out.println("Sie konnten leider nicht entkommen und das Monster hat Sie angegriffen!");
                        monster.greifeAn(spieler);
                        System.out.println("\nWas möchten Sie machen? Weiterkämpfen oder noch einmal versuchen zu fliehen?");
                    }
                    else
                    {
                        konnteFliehen = true;
                        entkamMonster();
                    }                
                }       
            }   
        }
        while((!spieler.istTot() && !monster.istTot()) && !konnteFliehen);
        
        if (!spieler.istTot() && !konnteFliehen)
            System.out.println("Sie haben den Kampf gewonnen und das Monster besiegt!"); 
    }
}

Ist es so besser? Könnte man noch verbessern oder ist das sogar noch schlechter, bzw. allgemein eher suboptimal?
Ich musste auch noch eine Parserinstanz erzeugen, damit ich die Befehle im kampf weiterhin einlesen kann.

Ich hoffe eigentlich vor allem auf Antworten in Bezug zu meinen Ansätzen. Es wäre dann gut, wenn ihr mir sagen könntet, warum das so nicht gut ist oder ob das so ein "akzeptabler" Weg ist oder ebend nicht.
 
Zuletzt bearbeitet von einem Moderator:
Zurück
Oben