Ich programmiere gerade einen Snakeklon mit dem Ziel, dies möglichst OO-getreu umzusetzen.
Das grundlegenste Spielprinzip ist bereits funktionsfähig (Schlange bewegt sich “Snaketypisch”, wird größer beim Essen usw.) allerdings habe ich einige Sachen unsauber umgesetzt.
Statt z.B. eine Klasse “Schlange” mit den privaten Variablen “positionY”, “positionY” usw zu erstellen und auf diese Variablen von außerhalb nur per getter zuzugreifen, habe ich public static Variablen in einer “SnakeSpiel” Klasse erstellt und keine “Schlange”-Klasse.
Dies will ich nun verbessern. Mein Problem ist aber, dass ich nicht genau weiß, wo und wie ich die Referenzvariable für das Objekt von der Klasse “Schlange” hinpacken soll, da bei mir 2 verschiedene Klassen auf dieses Objekt bzw. die Referenzvariable zugreifen können müssen. Dazu müsste ich die Referenzvariable von dem Schlangenobjekt doch static machen oder? Wäre dies denn dann noch OO-getreu? Falls ja, wo instanziiere ich die Schlangenreferenz am besten? Ist das egal wo?
Du brauchst dafür nichts statisches. Wenn du ein statisches Feld hast, dann kannst du nur genau ein Spiel gleichzeitig spielen (oder sogar nur eine Schlange gleichzeitig, je nachdem, wo was static ist).
Wie wäre es mit einem derartigen Interface für die Spielwelt:
Snake getSnake();
}```
Eine Instanz von der Spielwelt kannst du beispielsweise in der `main()`-Methode erstellen.
[QUOTE=cmrudolph]Du brauchst dafür nichts statisches. Wenn du ein statisches Feld hast, dann kannst du nur genau ein Spiel gleichzeitig spielen (oder sogar nur eine Schlange gleichzeitig, je nachdem, wo was static ist).
[/QUOTE]
Stimmt.
[QUOTE=cmrudolph;28326]
Wie wäre es mit einem derartigen Interface für die Spielwelt:
Snake getSnake();
}```
Eine Instanz von der Spielwelt kannst du beispielsweise in der `main()`-Methode erstellen.[/QUOTE]
Das verwirrt mich jetzt komplett ehrlich gesagt...
Ich hatte eher sowas hier im Kopf:
public class Schlange {
public Schlange schlange;
// Variablen, alle private…Auf diese Variablen sollen mehrere andere Klassen zugreifen können (eben über die Referenzvariable schlange, welche noch instanziiert werden muss (z.B. in der main). Allerdings haben andere Klassen auf “schlange” ja so in dem Fall hier keinen Zugriff. Das ist mein Problem.
Wäre “Schlange” nicht ein super Beispiel für ein Singleton?
private static final Schlange THE_INSTANCE;
static {
THE_INSTANCE = new Schlange();
}
private Schlange() {
}
public static Schlange getInstance() {
return THE_INSTANCE;
}
}```Kann sein, dass in einem Studiengang das Thema damit auf das Designpattern Singleton gelenkt wird. Danach folgt dann der Zweispielermodus (Rasterbikes) und die kaum zu leugnende Tatsache, dass Singleton anscheinend doch ein Designfehler war.
[QUOTE=Spacerat]Wäre “Schlange” nicht ein super Beispiel für ein Singleton?
private static final Schlange THE_INSTANCE;
static {
THE_INSTANCE = new Schlange();
}
private Schlange() {
}
public static Schlange getInstance() {
return THE_INSTANCE;
}
}```Kann sein, dass in einem Studiengang das Thema damit auf das Designpattern Singleton gelenkt wird. Danach folgt dann der Zweispielermodus (Rasterbikes) und die kaum zu leugnende Tatsache, dass Singleton anscheinend doch ein Designfehler war.[/QUOTE]
Bei nur einem Objekt verstehe ich das nun. Aber was ist, wenn ich mehr als 1 Schlangenobjekt erstellen will? Wie würde ich dies dann umsetzen, sodass mehrere andere Klassen Zugriff auf die Schlangen-Referenzvariablen haben?
In diesem Fall ists glücklicherweise noch recht einfach, du übergibst einfach irgendwelche ID-Variablen (z.B. ints oder Enums). Die einzelnen Instanzen speicherst du dann in einer Map
[QUOTE=Spacerat]In diesem Fall ists glücklicherweise noch recht einfach, du übergibst einfach irgendwelche ID-Variablen (z.B. ints oder Enums). Die einzelnen Instanzen speicherst du dann in einer Map
[/QUOTE]
Mit Enums kenne ich mich (noch) nicht aus. Aber wäre es nicht auch möglich, einfach eine statische ArrayList zu erstellen, welche dann die Objektreferenzvariablen beinhaltet?
Auf gar keinen Fall! Wie kann er dann später einen Multiplayermodus implementieren? (Ok, ich hab es jetzt erst gelesen, aber trotzdem ist der Punkt markant!) @Spacerat : nimms mir nicht übel, aber mit dem Ansatz bringst du ihn in Teufels Küche.
Wenn er dann einen Vierspielermodus haben möchte, oder einen Hundertspielermodus mit einem riesigen Schlangenhaufen. Soll er dann 100 Enums machen? Oder soll er noch einen Schritt zurück gehen und einfach einen Int als Identifikator nehmen?
Wenn man die Anwendung ins extreme zieht, dann bemerkt man derartige Designschwächen sehr schnell.
@Jack159 : sry, wenn ich dich mit dem Interface verwirrt habe. Ich mach das dann mal ohne Interface:
private Snake snake;
private Position snakePosition;
private Terrain[][] map;
// hier würde das oben genannte Interface implementiert
public Snake getSnake() {
return snake;
}
private boolean detectCollision() {
// ...
}
}
public class Position {
private int x;
private int y;
public Position(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
}
public enum Terrain {
WALL, ITEM .....;
}
Da fehlt noch jede Menge, das ist wohl klar, aber ich hoffe, dass grob herauslesbar ist, wie man auf die Schlange zugreifen kann.
[QUOTE=cmrudolph]Oder soll er noch einen Schritt zurück gehen und einfach einen Int als Identifikator nehmen?[/QUOTE]Genau. Allerdings finde ich Objekte als ID viel besser, weil dann meistens ein einfacher NullCheck als Fehleranalyse reicht. Es müssen ja keine Enums sein, aber zumindest Immutables (z.B. Strings oder andere CharSequencen).
[QUOTE=Jack159;28349]Mit Enums kenne ich mich (noch) nicht aus. Aber wäre es nicht auch möglich, einfach eine statische ArrayList zu erstellen, welche dann die Objektreferenzvariablen beinhaltet?[/QUOTE]Also ja, du könntest auch, wie gesagt, ganz simpel ints statt Enums an “forID()” übergeben.
[QUOTE=Spacerat]
Also ja, du könntest auch, wie gesagt, ganz simpel ints statt Enums an “forID()” übergeben.[/QUOTE]
Wieso brauche ich denn die forID() Methode? Und was ist überhaupt der Sinn von dieser bzw. deinem ganzen gepostetem Beispiel?
Ich würde einfach sowas hier machen:
class Schlange {
public static ArrayList<Schlange> schlangen = new ArrayList<Schlange>();
.....
}
Diese ArrayList ist dann von überall aus aufrufbar und ich kann jederzeit von überall Schlangenobjekte darin erzeugen, welche von überall aus aufrufbar sind.
Bei dem mit dem Singleton hab’ ich etwas verzweifelt und irritiert nach dem :o)-Smiley gesucht. Und … das mit der ID leuchtet mir auch nicht ganz ein. Wer solllte so eine ID besitzen? Und warum sollte derjenige nicht direkt eine Referenz auf die Schlange besitzen?
Ganz allgemein in bezug auf die ursprüngliche Frage…
class KlasseDieSchlangeBraucht
{
private Schlange schlange;
public void setSchlange(Schlange schlange)
{
this.schlange = schlage;
}
}
// Wo die Schlange erstellt wird:
Schlange schlange = new Schlange();
instanzVonKlasseDieSchlangeBraucht.setSchlange(schlange);
oder, die umgekehrte Richtung, über einen Getter. Poste ggf. mal ein bißchen vom aktuellen Code.
@Jack159 : hast du dir auch meinen Quellcode angesehen?
Eine public static Variable in einer Klasse zu haben entspricht in etwa einer globalen Variablen. Das ist auf jeden Fall zu vermeiden.
Jedes Objekt sollte nur Informationen über sich selbst haben. Daher habe ich die Schlangenposition auch in das Spielfeld und nicht in die Schlange modelliert.
Wenn du mehrere Schlangen haben möchtest, kannst du sie direkt jeweils einem Spieler zuordnen.
Beispiel mit einer Table aus der Guava-Bibliothek:
private Table<Player, Position, Snake> snakes;
}```
Ohne Guava:
```public class GameWorld {
private Map<Player, Map<Position, Snake>> snakes;
}```
In deiner main-Methode hast du dann eine Instanz von der Spielwelt und diese Instanz gibt dir die Schlangen zurück (inkl. deren Position). Du siehst, an der Schlange selbst braucht man nichts zu ändern. Wenn du jetzt die Implementierung veröffentlicht hättest, indem du ein public-Feld deklariert hättest, hättest du nicht ohne umfangreiche Quellcodeänderungen an anderen Stellen die Interna umstellen können.
[QUOTE=Jack159]Wieso brauche ich denn die forID() Methode? Und was ist überhaupt der Sinn von dieser bzw. deinem ganzen gepostetem Beispiel?
…
Diese ArrayList ist dann von überall aus aufrufbar und ich kann jederzeit von überall Schlangenobjekte darin erzeugen, welche von überall aus aufrufbar sind.[/QUOTE]Der Sinn liegt ganz einfach darin, dass die Klasse “Schlange” selber für die Verwaltung ihrer Instanzen verantwortlich ist. Da deine statische Methodik wie du schon sagst von überall her geändert werden kann, können z.B. auch Instanzen in mehreren Threads erzeugt werden, wodurch es dann unter Umständen zu viele werden würden (also z.B. 8 für nur 4 Spieler). Das geht bei meiner Version nicht.
Hier liegt gerade viel Spekulation (und, mit Verlaub: abenteuerlichSTe Konstrukte) in der Luft… ein bißchen mehr Code, und vor allem die Antwort auf die Fragen, WER die Schlange-Instanzen erstellt und WER Referenzen darauf braucht, könnte helfen…
Die Referenz des Schlangeobjekts braucht bei mir die Klasse „GUI“ (dort die paintComponent()) und die Klasse „Spielschleife“ (dort eben die Gameloop).
Das Schlangenobjekt würde ich in der GUI Klasse nach der 50. Zeile erstellen.
Ich poste einfach mal den gesamten aktuellen Code für intressierte:
App:
import java.awt.EventQueue;
public class App { //21:43
public static GUI gui;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
gui = new GUI();
gui.erstelleStartFenster();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
import java.awt.Rectangle;
import java.util.ArrayList;
public class SnakeSpiel {
public static boolean gestartet = false;
public static boolean beendet = false;
public static int schlangeX = 100;
public static int schlangeY = 100;
public static boolean schlangeOben = false;
public static boolean schlangeUnten = false;
public static boolean schlangeLinks = false;
public static boolean schlangeRechts = true;
public static int essenX = 0;
public static int essenY = 0;
public static boolean essenGesetzt = false;
public static int punktzahl = 0;
public static ArrayList<Rectangle> liste = new ArrayList<Rectangle>();
public void starteSpiel() {
gestartet = true;
App.gui.erstelleSpielFenster();
spielSchleife();
}
public void spielSchleife() {
Thread t = new Thread(new Spielschleife());
t.start();
}
public static void spielBeenden() {
gestartet = false;
beendet = true;
}
}
Spielschleife:
import java.awt.Rectangle;
public class Spielschleife implements Runnable {
public void run() {
SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
while(SnakeSpiel.gestartet==true) {
//Steuerungsabfragen und Kollisionsabfragen mit den Wänden
if(SnakeSpiel.schlangeOben==true) {
if(SnakeSpiel.schlangeY<5) {
SnakeSpiel.spielBeenden();
}else{
SnakeSpiel.schlangeY -= 5;
}
}else if(SnakeSpiel.schlangeUnten==true) {
if(SnakeSpiel.schlangeY>350) {
SnakeSpiel.spielBeenden();
}else {
SnakeSpiel.schlangeY += 5;
}
}else if(SnakeSpiel.schlangeLinks==true) {
if(SnakeSpiel.schlangeX<5) {
SnakeSpiel.spielBeenden();
}else {
SnakeSpiel.schlangeX -= 5;
}
}else if(SnakeSpiel.schlangeRechts==true) {
if(SnakeSpiel.schlangeX>370) {
SnakeSpiel.spielBeenden();
}else {
SnakeSpiel.schlangeX += 5;
}
}
//Essen platzieren, falls nicht gesetzt
if(SnakeSpiel.essenGesetzt == false) {
SnakeSpiel.essenX = (int) (5+Math.random()*365);
SnakeSpiel.essenY = (int) (5+Math.random()*345);
System.out.println(SnakeSpiel.essenX);
SnakeSpiel.essenGesetzt = true;
}
//Hat Schlange das Essen berührt? Falls ja, Schlange vergrößern
if(Math.abs(SnakeSpiel.essenX-SnakeSpiel.schlangeX)<=8 && Math.abs(SnakeSpiel.essenY-SnakeSpiel.schlangeY)<=8) {
System.out.println("Kollsion");
SnakeSpiel.essenGesetzt = false;
if(SnakeSpiel.schlangeLinks == true) {
SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
}else if(SnakeSpiel.schlangeRechts == true) {
SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
}else if(SnakeSpiel.schlangeOben == true) {
SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
}else if(SnakeSpiel.schlangeUnten == true) {
SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
}
}
//Schlangenteile korrekt "weiterreichen" bzw. Koordinaten weiterreichen, damit sich die Schlange korrekt bewegt ->
// Dazu das Schlangenende entfernen und einen neuen Kopf hinzufügen. Dadurch bewegt sich die Schlange um eins weiter
SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
SnakeSpiel.liste.remove(0);
System.out.println("liste.size(): "+SnakeSpiel.liste.size());
GUI.f1.repaint();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
OK, im GUI wird auf einen Buttonklick hin mit
SnakeSpiel snakespiel = new SnakeSpiel();
das Spiel-Objekt erstellt und mit starteSpiel gestartet. Beim Starten wird nochmal so ein Rückwärtssalto ins GUI gemacht, mit
App.gui.erstelleSpielFenster();
Und danch wird das “Spielschleife”-Objekt erstellt und als Thread gestartet.
Ein erster Schritt könnte sein, das
public void erstelleSpielFenster()
nicht aus dem Spiel-Objekt aufrzurufen, sondern direkt aus dem GUI, aus der ActionPerformed, wo auch schon das SnakeSpiel erstellt wird:
public void actionPerformed(ActionEvent arg0) {
SnakeSpiel snakespiel = new SnakeSpiel();
erstelleSpielFenster(snakespiel); // Instanz übergeben
snakespiel.starteSpiel();
}
public void erstelleSpielFenster(final Snakespiel snakespiel) { // Instanz kommt hier an
...
f1.addKeyListener(new KeyListener() {
...
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==38) {
System.out.println("Pfeiltaste oben");
snakeSpiel.schlangeUnten=false; // Instanz wird hier verwendet
...
Ganz genauso könnte in der “starteSpiel” das Objekt an die Spielschleife übergeben werden:
Thread t = new Thread(new Spielschleife(this)); // Instanz übergeben
public class Spielschleife implements Runnable {
private Snakespiel snakespiel;
public Spielschleife(Snakespiel snakespiel) // Instanz wird hier übergeben und gespeichert
{
this.snakespiel = snakespiel;
}
public void run() {
// Instanz wird hier verwendet
snakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
...
Das ist natürlich nur KURZ angerissen, wie man das “static” erstmal weitgehend wegbekommen könnte. Weitere strukturelle Änderungen (wie z.B. eine eigene Klasse “Schlange” usw.) kann man sich noch überlegen (und ggf. gleich einbauen…)
[QUOTE=Marco13]OK, im GUI wird auf einen Buttonklick hin mit
SnakeSpiel snakespiel = new SnakeSpiel();
das Spiel-Objekt erstellt und mit starteSpiel gestartet. Beim Starten wird nochmal so ein Rückwärtssalto ins GUI gemacht, mit
App.gui.erstelleSpielFenster();
Und danch wird das „Spielschleife“-Objekt erstellt und als Thread gestartet.
Ein erster Schritt könnte sein, das
public void erstelleSpielFenster()
nicht aus dem Spiel-Objekt aufrzurufen, sondern direkt aus dem GUI, aus der ActionPerformed, wo auch schon das SnakeSpiel erstellt wird:[/QUOTE]
Danke für die Hilfe, ich habe dies nun erstmal so umgesetzt. Die Klasse Schlange werde ich noch einbauen, da sie denke ich perfekt passen würde. Und für die Ursprüngliche Frage hatte ich genau sowas in der Art gesucht, wie hier auf dieser Seite ganz oben von dir gepostet wurde.