Spiel ist langsam/laggt

Witschi262

Mitglied
Hallo,
für ein Projekt in der Schule müssen wir ein Spiel programmieren. Das Projekt ist schon sehr weit, doch wie befürchtet ist es sehr langsam und hängt oft.

Es ist ein Multiplayer Spiel, bei dem zwei Schlangen gegeneinander antreten. Es ist ein Gitter aus Quadraten. Das Quadrat, auf dem die Schlange sich befindet ist farbig. Pro Zeiteinheit, soll die Schlange sich nach vorne bewegen, dabei aber ihre anfangsposition nicht verändern (wird also immer länger). Wer zuerst die Wand/die andere Schlange berührt hat verloren.

Der komplette Quellcode:

Java:
import javax.swing.Timer;
import java.awt.event.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
public class SPIEL extends JFrame implements KeyListener {

    protected int width = 51;
    protected int height = 30;

    private ImageIcon empty_square = new ImageIcon(System.getProperty("user.dir")+"\\square.png");
    private ImageIcon red_square = new ImageIcon(System.getProperty("user.dir")+"\\square_red.png");
    private ImageIcon blue_square = new ImageIcon(System.getProperty("user.dir")+"\\square_blue.png");
    private ImageIcon bonus_square = new ImageIcon(System.getProperty("user.dir")+"\\square_bonus.png");

    private JPanel test;

    protected ImageIcon grid[][] = new ImageIcon[height][width];
    protected ImageIcon set_grid[][] = new ImageIcon[height][width];

    private String pos_1;
    private String pos_2;
            String pos_3;

    private String dir_1 = "l";
    private String dir_2 = "r";

    Timer timer;

    public SPIEL()
    {  
        timer = new Timer(100, new ActionListener() {
                public void actionPerformed(ActionEvent e) { 
                    move();
                }  
            }); 
        test = new JPanel();
        test.setBackground(Color.black);
        test.setBounds(0,0,width*20+width*5,height*20+height*5);
        test.setVisible(true);

        build(3,3, blue_square);
        build(47,26, red_square);

        build(random(grid[0].length), random(grid.length), bonus_square);
        build(random(grid[0].length), random(grid.length), bonus_square);
        build(random(grid[0].length), random(grid.length), bonus_square);

        test.revalidate();

        JFrame frame = new JFrame("Schlangenspiel");
        frame.setLayout(null);
        frame.setExtendedState(Frame.MAXIMIZED_BOTH); 
        frame.getContentPane().setBackground(Color.black);
        frame.setVisible(true);
        frame.addKeyListener(this);
        frame.add(test, 0);
    }

    // Erstellen des Rasters und das Einfärben ausgwählter Zellen mit ausgewählter Farbe und übernimmt teilweise die Gewinnkontrolle
    public void build(int x_pos, int y_pos, ImageIcon i)
    {
        if(x_pos >= grid[0].length  || y_pos >= grid.length || x_pos < 0 || y_pos < 0)
        {
            timer.stop();
            return;
        }

        if(set_grid[y_pos][x_pos] != null && set_grid[y_pos][x_pos] != bonus_square)
        {
            timer.stop();
        } else {
            if(set_grid[y_pos][x_pos] == bonus_square)
            {
                set_grid[y_pos][x_pos] = i;
                build(random(grid[0].length), random(grid.length), bonus_square);
                Bonus();
            }
            set_grid[y_pos][x_pos] = i;
        }
        

        for(int x = 0; x < grid.length; x++)
        {
            for(int y = 0; y < grid[0].length; y++)
            {

                grid[x][y] = empty_square; 

                if(set_grid[x][y] != null)
                {
                    grid[x][y] = set_grid[x][y];

                    if(i == blue_square)
                    {
                        pos_1 = String.valueOf(x_pos) + ";" + String.valueOf(y_pos);
                    } else { 
                        if(i == red_square) 
                        { pos_2 = String.valueOf(x_pos) + ";" + String.valueOf(y_pos);
                        }
                    }
                }
                test.add(new JLabel(grid[x][y]), 0);
            }
        } 
        test.revalidate();
    }

    // Erstellt eine Zufallszwischen zwischen 1 und max-1
    private int random(int max)
    {
        int random = (int) (Math.random()*max);  
        return random;
    }

    /**
     * Tastensteuerung der Schlangen
     */
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        switch( keyCode ) { 
            case KeyEvent.VK_UP:
            dir_1 = "u";
            break;

            case KeyEvent.VK_DOWN:
            dir_1 = "d";
            break;

            case KeyEvent.VK_LEFT:
            dir_1 = "l";
            break;

            case KeyEvent.VK_RIGHT :
            dir_1= "r";
            break;

            case KeyEvent.VK_W:
            dir_2= "u";
            break;

            case KeyEvent.VK_S:
            dir_2= "d";
            break;

            case KeyEvent.VK_A:
            dir_2= "l";
            break;

            case KeyEvent.VK_D :
            dir_2= "r";
            break;
            
            case KeyEvent.VK_SPACE :
            timer.start();
            break;
        }
    } 

    public void keyTyped(KeyEvent e){}

    public void keyReleased (KeyEvent e){

    }

    public int getWidth()
    {
        return width;
    }

    public int getHeight()
    {
        return height;
    }

    private void move() {
        String[] move_1 = pos_1.split(";");
        String[] move_2 = pos_2.split(";");

        if(dir_1.equals("r"))
        {
            build(Integer.parseInt(move_1[0]) - 1, Integer.parseInt(move_1[1]), blue_square);
        }

        if(dir_1.equals("l"))
        {
            build(Integer.parseInt(move_1[0]) + 1, Integer.parseInt(move_1[1]), blue_square);
        }

        if(dir_1.equals("u"))
        {
            build(Integer.parseInt(move_1[0]), Integer.parseInt(move_1[1]) + 1, blue_square);
        }

        if(dir_1.equals("d"))
        {
            build(Integer.parseInt(move_1[0]), Integer.parseInt(move_1[1]) - 1, blue_square);

        }      
        
        if(dir_2.equals("r"))
        {
            build(Integer.parseInt(move_2[0]) - 1, Integer.parseInt(move_2[1]), red_square);
        }

        if(dir_2.equals("l"))
        {
            build(Integer.parseInt(move_2[0]) + 1, Integer.parseInt(move_2[1]), red_square);
        }

        if(dir_2.equals("u"))
        {
            build(Integer.parseInt(move_2[0]), Integer.parseInt(move_2[1]) + 1, red_square);
        }

        if(dir_2.equals("d"))
        {
            build(Integer.parseInt(move_2[0]), Integer.parseInt(move_2[1]) - 1, red_square);
        }
    }
    
    private void Bonus() {
        
    }
}

Wir werden den Quellcode noch aufteilen in verschiedene Klassen, Fehler beheben aber wollen eben auch, dass es spielbar ist.

Wir haben leider keinen anderen Weg gefunden, als den Array grid in der Build() Methode jedes mal neu aufzubauen und das Gitter neu zu generieren. Dadurch, dass der Array set_grid pro Spielzug genau 2x länger wird und in der langen for-Schleife dann pro Spielzug auf 2 Abfragen mehr stattfinden, läuft es etwa 5 Sekunden flüssig und wird dann immer langsamer.

Meine Frage ist jetzt: Wie können wir evtl. das aufbauen des Gitters verändern bzw. das Gameplay flüssiger gestalten?

Grüße,
Witschi
 
Warum ist dir... Ein string, wenn du da nur zahlen rein schreibst, und das dann eh nach int parst?
Mach das doch gleich mit ints.
Mach dir eine Liste mit ints, anstelle eines strings, den du immer weiter in ein Array zerschneidest...

Du musst da viel umkopieren und neu erzeugen. Das ist nichtv so performant
 
Odet, wenn ihr noch nichts mit Listen gemacht habt, dann nimm ei integer array, mit der grösse der Felder durch 2 und mach die beiden arrays member von SPIEL, nicht von der move Methode
 
Ich kann mir gut vorstellen, dass
Code:
test.add(new JLabel(grid[x][y]), 0);
beim permanenten Aufruf und durch die Schleife sehr kosenintensiv ist. Kannst es mal zum Testen durch ein
Code:
JLabel[][] abc
ersetzen .... das Image setzst du dann mit
Code:
abc[x][y].setIcon(grid[x][y])
.
 
Hallo,
okay. Die Frage ist, wie ich dann den JLabel Array Adde? frame.add(abc) geht ja so einfach nicht. Müsste das ja auch in die For Schleife packen.
 
Okay, das habe ich jetzt gemacht, habe aber den Array move_1/2[][] entfernt und einfach 4 Integer erstellt. Die sollen ja nur die aktuelle Position enthalten (pos_1_x, pos_1_y etc.)
 
so noch mal geguckt.

Mach dir doch eine Klasse "Spieler"
diese Klasse bietet ein Array von Punkten, an denen der Spieler schon war.

Das instanzierst du 2 mal.

und dann fragst du einfach in deiner kollisionsabfrage, ob der Spieler schon an der Stelle war.
(Du musst ja immer nur die neueste Stelle mit allen vergleichen, wo der andere war)

das heisst:

Spieler#boolean testePosition(int x, int y)

Spieler hat ein Array von Positionen, an denen er war.

da gehst du einfach drüber. Du kannst auch schauen, dass du intelligent prüfst:
schau erstmal, ob du überhaupt schon einen Wert übereinstimmend hsat, und prüfe nur an der Stelle weiter
 
Wie soll ich den Array am besten erstellen? Ein zweidimensionaler int[][], indem ich die Koordinaten speichere erachte ich für sinnlos, da ich ja dann eigentlich wieder über eine For Schleife erst mal suchen müsste, wo überhaupt das Feld belegt ist.

Belegte Felder sind ja auch im set_grid[][] enthalten.
 
Hallo,
vielen Dank für die Hilfe. Ich konnte es Ruzmanz antwort sehr gut lösen.

Hier die beiden wichtigen Methoden:

Java:
   /**
     * Erstellen des Rasters und der Ausgangslage (Startpositionen und Bonusfelder)
     */
    public void build(int x_pos, int y_pos, ImageIcon i)
    {
        set_grid[x_pos][y_pos] = i;

        for(int y = 0; y < grid[0].length; y++)
        {
            for(int x = 0; x < grid.length; x++)
            {

                grid[x][y] = empty_square; 

                if(set_grid[x][y] != null)
                {
                    grid[x][y] = set_grid[x][y];

                    if(i == blue_square)
                    {
                        pos_1_x = x_pos;
                        pos_1_y = y_pos;
                    } else { 
                        if(i == red_square) 
                        {                         
                            pos_2_x = x_pos;
                            pos_2_y = y_pos;
                        }
                    }
                }
                grid_content[x][y] = new JLabel();
                grid_content[x][y].setIcon(grid[x][y]);
                grid_layout.add(grid_content[x][y],0);
            }
        } 
        grid_layout.revalidate();
    }

    /**
     * Setzen eines Bildes i an die Positionen X und Y
     */
    public void set(int posx, int posy, ImageIcon i)
    {
        if(posx >= width || posy >= height || posx < 0 || posy < 0)
        {
            
                timer.stop();
                win(i);

                return;
            
        }

        if(i == blue_square)
        {
            pos_1_x = posx;
            pos_1_y = posy;
        } else { 
            if(i == red_square) 
            {                         
                pos_2_x = posx;
                pos_2_y = posy;
            }
        }
        
        if(set_grid[posx][posy] != bonus_square && set_grid[posx][posy] != null)
        {
            timer.stop();
            win(i);
            return;
        }

        if(set_grid[posx][posy] == bonus_square)
        {
            Bonus(i);
            set(random(grid.length), random(grid[0].length), bonus_square);
        }      

        grid_content[posx][posy].setIcon(i);
        set_grid[posx][posy] = i;
        grid_layout.revalidate();
        grid_content[posx][posy].revalidate();
    }
 
Allgemein kann man sagen 8
1. kann man eine Menge RAM sparen, wenn man anstatt int's short's, oder byte's verwendet, sofern man keine größeren Werte erwartet,
2. lässt sich eine Menge Zeit sparen, indem man mehrere if-Abfragen zur selben Variablen entweder mit "else if", oder besser mit einem Switch löst,
 
Allgemein kann man sagen 8
1. kann man eine Menge RAM sparen, wenn man anstatt int's short's, oder byte's verwendet, sofern man keine größeren Werte erwartet,
2. lässt sich eine Menge Zeit sparen, indem man mehrere if-Abfragen zur selben Variablen entweder mit "else if", oder besser mit einem Switch löst,

Man spart genau 0, wenn man statt int byte nutzt. Beides braucht gleich viel Speicher, nämlich 32 bzw 64 Bit als kleinste Einheit. Also 4 bzw 8 Byte.
Lediglich double und long sind anders, die brauchen doppelt so viel speicher.

Performance wird von aufwändigen Operationen gefressen, wie eben zu tiefe verschachtelungen sowie dauerndem umkopieren von immutables
 
😡: Phash 8
Ich korrigiere 8
Java:
Typ		Größe	Wertebereich
byte	8  Bit	-128				bis	+127
short	16 Bit	-32.768				bis	+32.767
int		32 Bit	ca. -2 Milliarden	bis	+2 Milliarden
long	64 Bit	ca. -10E18			bis	+10E18
float	32 Bit	-3.4E+38			bis +3.4E+38
double	64 Bit	-1.7E+308			bis	1.7E+308
 
Zuletzt bearbeitet:
@Phash und @Androbin
Ist beides falsch. Der RAM-Verbrauch hängt vom Compiler und der JRE ab. Prinzipell bietet es sich an die kleinstmögliche Einheit zu nehmen, aber eine Speicherspar-Garantie gibt es nicht. Im Worst-Case verbraucht man tatsächlich für jeden Wert 32bit oder 64bit (Hardwareabhängig). Eine Möglichkeit zur Reduzierung des Speicherverbrauchs ist die einzelnen Variablen in Blöcken zu sammeln:

- an instance of a class with a single boolean field takes up 16 bytes: 8 bytes of header, 1 byte for the boolean and 7 bytes of "padding" to make the size up to a multiple of 8;
Siehe: Memory usage of Java objects: general guide

In C kann man wunderbar nachvollziehen (Kompilerabhängig), warum
Code:
byte a, int b, byte c
mehr Speicher verbraucht als
Code:
byte a, byte c, int b
. (Die JRE macht das evt. automatisch, evt. auch nicht ... zumindest kann man es mMn nicht beeinflussen)

Selbst Oracle gibt keine offizellen Größen an, sondern spricht nur von Speicherplatzeinsparungen bei größeren Arrays: Primitive Data Types (The Java™ Tutorials > Learning the Java Language > Language Basics) und dort kann man übrigens auch entnehmen, dass die Wertebereiche für int und long erweitert wurden.
 
Zuletzt bearbeitet:
Afaik ist der smallest der chunk, der allokiert wird ein 4 bzw 8 byte Stück.

Nichts desto trotz, spart man sich bei Rechnungen in den Dimensionen, um die es hier geht, nichts.
Man wird keinen unterschied zwischen byte, short und int merken.
 
Primitive Datentypen
Die JVM ist ein 32 Bit System. Aus diesem Grund sind Zugriffe auf Variablen mit einer Breite von 32 Bit am schnellsten. Somit ist der Zugriff auf Referenzen und int Variablen besonders schnell. float Variablen, sind Fliesskommazahlen und können nicht ganz so schnell verarbeitet werden. Die primitiven Datentypen unterscheidet man auch in Datentypen erster Klasse und Datentypen zweiter Klasse. Datentypen erster Klasse sind int, long, float und double. Für diese Datentypen liegt ein kompletter Satz an arithmetischen Funktionen vor, so dass Berechnungen relativ zügig vonstattengehen. Die Datentypen zweiter Klasse boolean, byte, short und char hingegen haben keine arithmetische Funktionen. Für Berechnungen werden diese innerhalb der JVM in den int Typ gecastet und nach Abschluss zurück umgewandelt. Dies führt zu erheblichen Zeitverzögerungen

Gefunden hier:
Das Performance-Handbuch: Implementierung mit Java ? Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher
 
Man wird keinen unterschied zwischen byte, short und int merken.

Da stimme ich dir voll und ganz zu. Ich wollte nur anmerken, dass man nicht pauschal sagen kann, dass es immer 32/64bit sind. Ehrlich gesagt interessiert es mich auch nicht so sonderlich. Meistens nutze ich sowieso int, damit man nicht immer hin und her casten muss ...

Dein verlinkter Text handelt von Java 1 / Java 2. Damit hatte ich nie zu tun. Evtl. ist es heute immer noch so, aber eigentlich hat sich in dem Bereich viel geändert. Deshalb sollte man davon ausgehen, dass die Quellen nicht stimmen. Dennoch bin ich stark davon überzeugt, dass die JRE besser optimieren kann, wenn immer ein passender Datentyp gewählt wurde.
 

Zurück
Oben