# Snake Game Verbessern



## PlzNichtSchlagen (15. Jan 2020)

Hey ich habe schonmal etwas hier gepostet, nun würde ich gerne fragen ob man dieses Spiel noch irgendwie verbessern könnte.
Edit: Ausserdem habe ich probleme das die Tasteneingaben ein wenig spät kommen das heisst wenn ich "UP" drücke kann ich nicht direkt "RIGHT" drücken. Weiss weiner wie man das besser machen könnte? Und ebenfalls passiert es immer das aus irgendwelchen Gründen die Schlange manchmal in sich reinfährt und stirbt. Was habe ich falsch gemacht?


MAIN KLASSE:


```
package game;

import java.awt.Dimension;
  

import javax.swing.JFrame;

public class Main {
  

public static void main(String[] args) {
    JFrame frame = new JFrame("Snake Komplexe Leistung");
    frame.setContentPane (new GamePanel());
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setResizable(false);
    frame.setPreferredSize(new Dimension(GamePanel.WIDTH, GamePanel.HEIGHT));
    frame.setVisible(true);
    frame.setLocationRelativeTo(null);
}
}
```


GAMEPANEL KLASSE:


```
package game;

import java.awt.Color;



import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import javax.swing.JPanel;

@SuppressWarnings("serial")
public class GamePanel extends JPanel implements Runnable, KeyListener {
  
    public static final int WIDTH = 400;
    public static final int HEIGHT = 400;
   //Render
    private Graphics2D g2d;
    private BufferedImage image;

  
    //Spiel Loop
    private Thread thread;
    private boolean running;
    private long targetTime;
  
    //Spiel Zeugs
    private final int SIZE = 10;
    private Entity kopf,apfel;
    private ArrayList<Entity> schlange;
    private int score;
    private int level;
    private boolean gameover;
    //Bewegung
    private int dx,dy;
  
    //Key input
    private boolean up,down,right,left,start;
  
  
    public GamePanel () {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setFocusable(true);
        requestFocus();
        addKeyListener(this);
      
    }
  
    @Override
    public void addNotify() {
        super.addNotify();
        thread = new Thread(this);
        thread.start();
    }
    private void setFPS(int fps) {
        targetTime = 1000 / fps;
    }
  
    @Override
    public void keyTyped(KeyEvent e) {
      

    }

    @Override
    public void keyPressed(KeyEvent e) {
        int k = e.getKeyCode();
      
        if (k == KeyEvent.VK_UP) up = true;
        if (k == KeyEvent.VK_DOWN) down = true;
        if (k == KeyEvent.VK_LEFT) left = true;
        if (k == KeyEvent.VK_RIGHT) right = true;
        if (k == KeyEvent.VK_ENTER) start = true;
      

    }

    @Override
    public void keyReleased(KeyEvent e) {
        int k = e.getKeyCode();
      
        if (k == KeyEvent.VK_UP) up = false;
        if (k == KeyEvent.VK_DOWN) down = false;
        if (k == KeyEvent.VK_LEFT) left = false;
        if (k == KeyEvent.VK_RIGHT) right = false;
        if (k == KeyEvent.VK_ENTER) start = false;

    }

    @Override
    public void run() {
        if(running) return;
        init();
        long startTime;
        long elapsed;
        long wait;
        while(running) {
            startTime = System.nanoTime();
          
            update();
            requestRender();
          
          
            elapsed = System.nanoTime() - startTime;
            wait = targetTime - elapsed  / 1000000;
            if(wait > 0) {
                try {
                    Thread.sleep(wait);
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
          
        }
    }
    private void init() {
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        g2d = image.createGraphics();
        running = true;
        setUplevel();
      
      
    }
    private void setUplevel() {
        schlange = new ArrayList<Entity>();
        kopf = new Entity(SIZE);
        kopf.setPosition(WIDTH / 2, HEIGHT/ 2);
        schlange.add(kopf);
      
        for(int i = 1;i < 3;i++) {
            Entity e = new Entity(SIZE);
            e.setPosition(kopf.getX() + (i * SIZE) , kopf.getY());
            schlange.add(e);
              }
        apfel = new Entity(SIZE);
        setApple();
        score = 0;
        gameover = false;
        level = 1;
        dx = dy = 0;
        setFPS(level * 10);
      
      
      
    }
    public void setApple() {
        int x = (int) (Math.random() *(WIDTH - SIZE));
        int y = (int) (Math.random() *(HEIGHT - SIZE));
        x = x - (x % SIZE);
        y = y - (y % SIZE);
        apfel.setPosition(x, y);
      
    }


@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    if (image != null) {
        g.drawImage(image, 0, 0, null);
    }
}


    private void requestRender() {
        render(g2d);
        repaint();




      
    }

    private void update() {
        if(gameover) {
            if(start) {
                setUplevel();
            }
          
            return;
        }
         if(up && dy == 0){
             dy = -SIZE;
             dx = 0;
    }
         if(down && dy == 0){
             dy = SIZE;
             dx = 0;
         }
         if(left && dx == 0){
             dy = 0;
             dx = -SIZE;
         }
         if(right && dx == 0 && dy != 0){
             dy = 0;
             dx = SIZE;
         }
      
         if(dx != 0 || dy != 0) {
         for (int i = schlange.size() - 1;i > 0;i--) {
          
             schlange.get(i).setPosition(
                     schlange.get(i - 1).getX(),
                     schlange.get(i - 1).getY()
                                      
                     );
          
          
         }
        kopf.move(dx, dy);
         }
      
         for(Entity e : schlange) {
             if(e.isCollsion(kopf)) {
                 gameover = true;
                 break;
              
              
             }
          
          
          
         }
      
         if(apfel.isCollsion(kopf)) {
             score++;
             setApple();
          
             Entity e = new Entity(SIZE);
             e.setPosition(-100,-100);
             schlange.add(e);
             if(score % 10 == 0) {
                 level++;
                 if(level > 10) level = 10;
                 setFPS(level * 10);
              
             }
          
          
          
         }
      
      
      
         if (kopf.getX() < 0) kopf.setX(WIDTH - 10);
         if (kopf.getY() < 0) kopf.setY(HEIGHT - 10);
         if (kopf.getX() > WIDTH - 10) kopf.setX(0);
         if (kopf.getY() > HEIGHT - 10) kopf.setY(0);
      
      
      
  
      
    }
    public void render(Graphics2D g2d) {
    g2d.clearRect(0, 0, WIDTH, HEIGHT); 
  
    g2d.setColor(Color.GREEN);
    for(Entity e : schlange) {
        e.render(g2d);
      
      
    }
    g2d.setColor(Color.RED);
    apfel.render(g2d);
    if(gameover) {
        g2d.drawString("GameOver!", 150, 200);
      
    }
  
    g2d.setColor(Color.WHITE);
    g2d.drawString("Score : " + score + "Level : " + level, 10, 10);
    if(dx == 0 && dy == 0) {
        g2d.drawString("Ready!", 150, 200);
    }
  
  
  
      
    }



}
```

ENTITY KLASSE:


```
package game;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Entity {
    private int x,y,size;
    public Entity(int size) {
        this.size = size;
      
    }
  
    public int getX(){
      
        return x;
      
    }
  
    public int getY(){
      
        return y;
      
    }
  
    public void setX(int x){
      
        this.x = x;
      
    }
  
    public void setY(int y){     
        this.y = y;
      
    }
  
    public void setPosition(int x,int y)
  
    {
        this.x = x;
        this.y = y;
    }
  
    public void move(int dx,int dy){
        x += dx;
        y += dy;
      
      
      
      
      
    }
  
    public Rectangle getBound(){
         return new Rectangle(x, y, size, size);
    }
  
    public boolean isCollsion(Entity o){
        if(o == this) return false;
        return getBound().intersects(o.getBound());
      
    }
    public void render(Graphics2D g2d) {
        g2d.fillRect(x + 1, y + 1, size - 2, size - 2);
      
    }
}
```


----------



## Blender3D (16. Jan 2020)

PlzNichtSchlagen hat gesagt.:


> Hey ich habe schonmal etwas hier gepostet, nun würde ich gerne fragen ob man dieses Spiel noch irgendwie verbessern könnte.


Habe den Code einmal etwas verfeinert.

```
import java.awt.Dimension;

import javax.swing.JFrame;

import game.GamePanel;

public class start {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Snake Komplexe Leistung");
        GamePanel game = new GamePanel(20);
        frame.setContentPane(game);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setPreferredSize(new Dimension(GamePanel.WIDTH, GamePanel.HEIGHT));
        frame.addKeyListener(game.getKeyBoard());
        frame.pack();
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);
        game.start();
    }
}
```


```
package game;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
import game.Snake.DIRECTION;

@SuppressWarnings("serial")
public class GamePanel extends JPanel implements Runnable {

    public static final int WIDTH = 600;
    public static final int HEIGHT = 600;
    private static final int MESSAGE_X = 150;
    private static final int MESSAGE_Y = 200;
    // Render
    private Graphics2D g2d;
    private BufferedImage image;

    // Spiel Loop
    private Thread thread;
    private boolean running;
    private long targetTime;

    // Spiel Zeugs
    private final int SIZE;
    private Entity apple;

    private int score;
    private int level;
    private boolean gameover;
    private boolean start = false;
    private Snake snake;

    public GamePanel(int size) {
        SIZE = size;
        setPreferredSize(new Dimension(WIDTH, HEIGHT));

    }

    public KeyAdapter getKeyBoard() {
        return new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int k = e.getKeyCode();

                if (k == KeyEvent.VK_UP)
                    snake.setDirection(DIRECTION.UP);
                if (k == KeyEvent.VK_DOWN)
                    snake.setDirection(DIRECTION.DOWN);
                if (k == KeyEvent.VK_LEFT)
                    snake.setDirection(DIRECTION.LEFT);
                if (k == KeyEvent.VK_RIGHT)
                    snake.setDirection(DIRECTION.RIGHT);
                if (k == KeyEvent.VK_ENTER)
                    start = true;
            }
        };
    }

    private void init() {
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        g2d = image.createGraphics();
        setFont(g2d.getFont().deriveFont(Font.BOLD, SIZE));
        running = true;
        setupLevel();

    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (image != null)
            g.drawImage(image, 0, 0, null);
    }

    private void render() {
        g2d.clearRect(0, 0, WIDTH, HEIGHT);
        snake.render(g2d);
        g2d.setColor(Color.RED);
        apple.render(g2d);
        showMessages();
    }

    @Override
    public void run() {
        if (running)
            return;
        init();
        long startTime;
        long elapsed;
        long wait;
        while (running) {
            startTime = System.nanoTime();
            update();
            render();
            repaint();
            elapsed = System.nanoTime() - startTime;
            wait = targetTime - elapsed / 1000000;
            if (wait > 0) {
                try {
                    Thread.sleep(wait);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void setApple() {
        int x = (int) (Math.random() * (WIDTH - SIZE));
        int y = (int) (Math.random() * (HEIGHT - SIZE));
        x = x - (x % SIZE);
        y = y - (y % SIZE);
        apple.setPosition(x, y);

    }

    private void setFPS(int fps) {
        targetTime = 1000 / fps;
    }

    private void setupLevel() {
        snake = new Snake(SIZE, WIDTH / 2, HEIGHT / 2);
        apple = new Entity(SIZE);
        setApple();
        score = 0;
        gameover = false;
        level = 1;
        setFPS(level * 10);
        start = false;
    }

    private void showMessages() {
        g2d.setFont(g2d.getFont().deriveFont(Font.BOLD, SIZE));
        if (gameover)
            g2d.drawString("GAME OVER!", MESSAGE_X, MESSAGE_Y);
        g2d.setColor(Color.WHITE);
        g2d.drawString("Score : " + score + "      Level : " + level, SIZE / 2, SIZE);
        if (!snake.isMoving())
            g2d.drawString("READY!", MESSAGE_X, MESSAGE_Y);
    }

    public void start() {
        running = false;
        if (thread != null)
            while (thread.isAlive())
                ;
        thread = new Thread(this);
        thread.start();
    }

    private void update() {
        if (gameover) {
            if (start)
                setupLevel();
            return;
        }
        snake.move();
        if (snake.isCollision()) {
            gameover = true;
            return;
        }

        if (snake.isCollision(apple)) {
            score++;
            setApple();
            snake.grow();
            if (score % 10 == 0) {
                level++;
                if (level > 10)
                    level = 10;
                setFPS(level * 10);
            }
        }
        snake.adaptBounds(WIDTH - SIZE, HEIGHT - SIZE);
    }

}
```


```
package game;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Entity {
    private int x;
    private int y;
    public final int size;
    private Rectangle bound = null;

    public Entity(int size) {
        this.size = size;
        bound = new Rectangle(0, 0, size, size);
    }

    public Rectangle getBound() {
        bound.x = x;
        bound.y = y;
        return bound;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public boolean isCollsion(Entity o) {
        if (o == this)
            return false;
        return getBound().intersects(o.getBound());
    }

    public void move(int dx, int dy) {
        x += dx;
        y += dy;
    }

    public void render(Graphics2D g2d) {
        g2d.fillRect(x + 1, y + 1, size - 2, size - 2);
    }

    public void setPosition(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

}
```


```
package game;

import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;

public class Snake {
    private ArrayList<Entity> snake = new ArrayList<Entity>();
    private int dx = 0;
    private int dy = 0;

    public enum DIRECTION {
        UP, DOWN, LEFT, RIGHT
    }

    public Snake(int size, int x, int y) {
        Entity head = new Entity(size);
        snake.add(head);
        head.setPosition(x, y);
        for (int i = 1; i < 3; i++) {
            Entity e = new Entity(size);
            e.setPosition(head.getX() + (i * size), head.getY());
            snake.add(e);
        }
    }

    public void adaptBounds(int width, int height) {
        Entity head = getHead();
        if (head.getX() < 0)
            head.setX(width - 10);
        if (head.getY() < 0)
            head.setY(height - 10);
        if (head.getX() > width - 10)
            head.setX(0);
        if (head.getY() > height - 10)
            head.setY(0);
    }

    private Entity getHead() {
        return snake.get(0);
    }

    public void grow() {
        Entity e = new Entity(getHead().size);
        e.setPosition(-100, -100);
        snake.add(e);
    }

    public boolean isCollision() {
        Entity head = getHead();
        for (Entity e : snake) {
            if (e.isCollsion(head))
                return true;
        }
        return false;
    }

    public boolean isCollision(Entity apple) {
        return getHead().isCollsion(apple);
    }

    public boolean isMoving() {
        return !(dx == 0 && dy == 0);
    }

    public void move() {
        if (dx == 0 && dy == 0)
            return;
        for (int i = snake.size() - 1; i > 0; i--)
            snake.get(i).setPosition(snake.get(i - 1).getX(), snake.get(i - 1).getY());
        getHead().move(dx, dy);
    }

    public void render(Graphics2D g) {
        g.setColor(Color.GREEN);
        for (Entity e : snake)
            e.render(g);
    }

    public void setDirection(DIRECTION dir) {
        int speed = getHead().size;
        if (dir == DIRECTION.UP && dy == 0) {
            dy = -speed;
            dx = 0;
        }
        if (dir == DIRECTION.DOWN && dy == 0) {
            dy = speed;
            dx = 0;
        }
        if (dir == DIRECTION.LEFT && dx == 0) {
            dy = 0;
            dx = -speed;
        }
        if (dir == DIRECTION.RIGHT && dx == 0 && dy != 0) {
            dy = 0;
            dx = speed;
        }
    }
}
```


----------



## MoxxiManagarm (16. Jan 2020)

Ich finde deine Logik für die Bewegung der Schlange nicht optimal. Stelle dir doch mal vor du hast eine Reihe von gleichgroßen Würfeln vor dir auf den Tisch. Du willst diese Reihe nach links verschieben. Nimmst du dann wirklich jeden Würfel und rückst ihn eins nach links? Ich würde einfach den Würfel ganz rechts wegnehmen und ihn nach links packen. Das ist eine konstante Laufzeit. Bei deiner Logik benötigst du immer länger für die Bewegung der Schlange je länger sie ist. Eventuell hat das auch schon was mit deinem Problemchen zu tun.

Im Fall von Snake würde ich also den Tail entfernen und ihn je nach aktueller Bewegungsrichtung an den Head "ankleben". Der "angeklebte" Stein ist dann der neue Head und der vorherige Head ist dann einfach Body. Eventuell kannst du dann noch überlegen ob du die ArrayList nicht ggf. durch eine FIFO Datenstruktur ersetzen willst.

Außerdem empfehle ich dir auf den Thread zu verzichten und stattdessen den Swing Timer einzusetzen https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/swing/Timer.html


----------



## Blender3D (16. Jan 2020)

MoxxiManagarm hat gesagt.:


> Ich finde deine Logik für die Bewegung der Schlange nicht optimal.


Stimmt zu 100%. Ich habe mich in erster Line darauf beschränkt seine Codestruktur zu verbessern. 


MoxxiManagarm hat gesagt.:


> Außerdem empfehle ich dir auf den Thread zu verzichten und stattdessen den Swing Timer einzusetzen


Ist keine gute Idee, da die Auflösung von Swing Timer zu niedrig ist.


----------



## MoxxiManagarm (16. Jan 2020)

Blender3D hat gesagt.:


> Ist keine gute Idee, da die Auflösung von Swing Timer zu niedrig ist.


Was meinst du mit Auflösung an der Stelle?


----------



## PlzNichtSchlagen (16. Jan 2020)

Achso ok, dankeschön für die schnellen Antworten. Ich bin neu hier und habe mich erstmal an etwas simpleres probiert. Aber vielen dank für die Antworten ich werde sie bei dem nächsten programm beachten.


----------



## Blender3D (16. Jan 2020)

MoxxiManagarm hat gesagt.:


> Was meinst du mit Auflösung an der Stelle?




```
timer = new Timer(1, this);
```
Erzeugt einen Timer der theoretisch 1000 mal pro Sekunde einen ActionListener aufruft.
Das bedeutetet die zeitliche Auflösung wäre im besten Fall 1 ms.
So wie der TE das gemacht hat ist das schon der richtige Weg. Da gibt es natürlich auch noch Potential zur Verbesserung z.B aktives Rendern anstatt repaint(), Die verlorenen Frames mitzählen um sie auszuglichen ..
Aber für das Snake Spiel ist das im Moment nicht so wichtig.


Eine Verbesserung in meinem Codevorschlag war auch z.B.


```
public Rectangle getBound() {
        bound.x = x;
        bound.y = y;
        return bound;
    }
```
versus

```
public Rectangle getBound(){
         return new Rectangle(x, y, size, size);
    }
```
Warum?
der Code des TE erzeugt jedes mal ein neues Rectangle pro Entity. In einer permanenten Schleife wie im Game Loop führt das zu einer unnötigen Belastung des Garbagescollectors.


----------



## PlzNichtSchlagen (16. Jan 2020)

Blender3D hat gesagt.:


> ```
> timer = new Timer(1, this);
> ```
> Erzeugt einen Timer der theoretisch 1000 mal pro Sekunde einen ActionListener aufruft.
> ...



Also vorerst möchte ich sagen das dieser Code natürlich besser ist, aber warum "spawnen" bzw erscheinen die Äpfel manchmal am äussersten Rand sodass man sie nur ganz wenig bis kaum erkennen kann. Also sie spawnwn ausserhalb der Auflösung. Weiterhin wenn die Schlange nach links fährt und ich gleichzeitig hoch und rechts drücke dann ist es direkt GameOver gibt es da nicht irgendwie eine Blockade das er nicht in die selbe "Line" in sich hineinfährt?


----------



## MoxxiManagarm (16. Jan 2020)

Ganz ehrlich, das Gleichgewicht zwischen Bewegung und Richtungswechsel empfand ich persönlich auch immer als größte Herausforderung bei Snake. Ich glaube das kannst du eigentlich nur für dich debuggen und für dich entscheiden. Eventuell musst du Richtungswechsel decouncen, Timer neustarten oder Ähnliches.


----------



## PlzNichtSchlagen (16. Jan 2020)

MoxxiManagarm hat gesagt.:


> Ganz ehrlich, das Gleichgewicht zwischen Bewegung und Richtungswechsel empfand ich persönlich auch immer als größte Herausforderung bei Snake. Ich glaube das kannst du eigentlich nur für dich debuggen und für dich entscheiden. Eventuell musst du Richtungswechsel decouncen, Timer neustarten oder Ähnliches.



Ok, ich kenne mich da nicht so aus. Wie würdest du es lösen (ich will es nicht kopieren ich will mir eher ein Beispiel ansehen). Und wie würde man deiner Meinung nach das Problem mit den Äpfel lösen?

Edit: würde das so gehen?

```
public void setApple() {
        int x = (int) (Math.random() * (500 - SIZE));
        int y = (int) (Math.random() * (500 - SIZE));
        x = x - (x % SIZE);
        y = y - (y % SIZE);
        apfel.setPosition(x, y);
```


----------



## MoxxiManagarm (16. Jan 2020)

PlzNichtSchlagen hat gesagt.:


> ich will mir eher ein Beispiel ansehen


Da gibt es doch bestimmt unzählige Beispiele im Internet.



PlzNichtSchlagen hat gesagt.:


> Wie würdest du es lösen [...] Und wie würde man deiner Meinung nach das Problem mit den Äpfel lösen?


Ich würde grundsätzlich noch mehr in Rastern denken und entwickeln - Stichwort: MVC. Ein Apfel und alle Körperteile der Schlange haben dann Koordinaten (Model). Speed ist dann der Zeitabstand in welchem ein Bewegungsschritt der Schlange passiert. Auf welchem Pixel und wie groß diese Teile dann gezeichnet werden übernimmt die GUI (View). Eine Positionsänderung ist dann immer nur um 1 bzw. -1 auf der jeweiligen Achse. Eine Kollision lässt sich dann einfach ermitteln auf Basis dieser Koordinaten (Überschreiben von Entity.equals() und dann Einsatz von Snake.snake.contains()). Aktuell übernimmt die Collision Detection deine GUI. Stell dir vor du würdest morgen dein Spiel von Swing auf JavaFX umstellen wollen. Mit MVC hättest du relativ einfach Spiel, aktuell no way.

Ein Apfel auf einer Koordinate wird dann auch genau auf einem Kachel des Rasters gespawnt.


----------



## MoxxiManagarm (16. Jan 2020)

Vielleicht hilft dir nochmal ein kleines Anschaungsbeispiel wie ich es meine.

Sagen wir du hast ein 3x3 großes Snake Raster. Ich habe keine Swing GUI, sondern nur Zeichen
ooo
ooo
ooo

Deine Schlange spawnt mit 2 Teilen und die Bewegungsrichtung ist default links. a ist hier der Head
ooo
oab
ooo

Außerdem spawnst du einen Apfel x, per Zufall an eine Koordinate, die nicht im Objekt Schlange vorhanden ist.
xoo
oab
ooo

Beim nächsten Timer-Tick (weshalb ich auch Swing Timer meinte) wird entsprechend der aktuell ausgewählten Bewegungsrichtung die theoretisch nächste Koordinate (k) ermittelt.

xoo
kab
ooo

Bevor du k in die Schlange einfügst überprüfst du folgendes:
- liegt k außerhalb des Rasters, hat als eine Koordinate -1 bzw. 3? (du bist mit der Wand kollidiert) --> Spiel verloren
- gleicht k einem Element e der Schlange (k.x == e.x && k.y == e.y)? (Schlange ist mit sich selbst kollidiert) --> Spiel verloren
- gleicht k dem Apfel? (Schlange hat Apfel gefressen) --> k wird in die Schlange eingefügt ohne den Schwanz zu verlieren. Neuer Apfel wird gespawnt
- nicht von all dem? k wird in die Schlange eingefügt und der Schwanz (oben b) wird entfernt

k ist nach oberen Raster dann der neue Kopf und b ist verschwunden

xoo
kao
ooo

Würdest du nun nicht die Richtung wechseln würdest du die Wand knutschen. Angenommen du hast nun die Richtung nach oben ausgewählt. Dann ist xka nun deine Schlange und ein neuer Apfel s wird irgendwo an stelle eines o gespawnt

xoo
kao
oos

usw.

Das Spiel ist gewonnen, wenn der Apfel nicht mehr gespawnt werden kann, es gibt also kein o mehr.

Du könntest z.B. verhindern, dass innerhalb eines Zeitschrittes die Richtung mehrmals geändert werden darf
Außerdem darf dabei nicht die genau gleiche bzw. entgegengesetzte Richtung gewählt werden (also nur oben und unten, falls aktuell links)

Ob nun die oben erwähnten Schritte von einer Konsole, von einer Swing app oder einer sonstigen Visualisierung dargestellt werden, ist eigentlich total schnuppe.


----------



## Blender3D (16. Jan 2020)

PlzNichtSchlagen hat gesagt.:


> Also vorerst möchte ich sagen das dieser Code natürlich besser ist, aber warum "spawnen" bzw erscheinen die Äpfel manchmal am äussersten Rand sodass man sie nur ganz wenig bis kaum erkennen kann. Also sie spawnwn ausserhalb der Auflösung. Weiterhin wenn die Schlange nach links fährt und ich gleichzeitig hoch und rechts drücke dann ist es direkt GameOver gibt es da nicht irgendwie eine Blockade das er nicht in die selbe "Line" in sich hineinfährt?


Habe den Code noch einmal refactored. Das mit dem Rand sollte behoben sein und die Blockade auch.
Ich habe für @MoxxiManagarm noch ein Beispiel mit animiertem Text eingefügt wie man den Swing Timer zustätzlich nützen kann.

*Wichtig alle Klassen habe sich etwas geändert also alle neu rein kopieren!*

```
import javax.swing.JFrame;
import game.GamePanel;

public class start {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Snake Komplexe Leistung");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GamePanel game = new GamePanel(25);
        frame.setContentPane(game);
        frame.setResizable(false);
        frame.addKeyListener(game.getKeyBoard());
        frame.pack();
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);
        game.start();
    }
}
```


```
package game;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.Random;

import javax.swing.JPanel;
import game.Snake.DIRECTION;

@SuppressWarnings("serial")
public class GamePanel extends JPanel implements Runnable {
    private static Random rnd = new Random(System.currentTimeMillis());
    public static final int WIDTH = 600;
    public static final int HEIGHT = 600;
    private static final int MESSAGE_X = WIDTH / 2 - 100;
    private static final int MESSAGE_Y = HEIGHT - 20;
    private static final int MAX_LEVEL = 10;
    private final int SIZE;
    // Render
    private Graphics2D g2d;
    private BufferedImage image;

    // Spiel Loop
    private Thread thread;
    private boolean running;
    private long targetTime;
    private final static int TXT_GAMEOVER = 0;
    private final static int TXT_STARTED = 1;

    private GameEffect[] text = { new FallingText("GAME OVER", WIDTH / 2 - 70, 0, HEIGHT, 70, true),
            new SnakeText("GOOD LUCK!", 0, HEIGHT / 2, WIDTH, 13) };

    // Spiel Zeugs

    private int score;
    private int level;
    private boolean gameover = true;

    private Snake snake;
    private Entity apple;

    public GamePanel(int size) {
        SIZE = size;
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
    }

    public KeyAdapter getKeyBoard() {
        return new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int key = e.getKeyCode();
                if (key == KeyEvent.VK_UP)
                    snake.setDirection(DIRECTION.UP);
                if (key == KeyEvent.VK_DOWN)
                    snake.setDirection(DIRECTION.DOWN);
                if (key == KeyEvent.VK_LEFT)
                    snake.setDirection(DIRECTION.LEFT);
                if (key == KeyEvent.VK_RIGHT)
                    snake.setDirection(DIRECTION.RIGHT);
                if (key == KeyEvent.VK_ENTER && gameover) {
                    text[TXT_STARTED].start(20);
                    resetLevel();
                    gameover = false;
                }
            }
        };
    }

    private void init() {
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        g2d = image.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        setFont(g2d.getFont().deriveFont(Font.BOLD, SIZE));
        running = true;
        resetLevel();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (image != null)
            g.drawImage(image, 0, 0, null);
    }

    private void render() {
        g2d.clearRect(0, 0, WIDTH, HEIGHT);
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, WIDTH, HEIGHT);
        snake.render(g2d);
        g2d.setColor(Color.RED);
        apple.render(g2d);
        showMessages();
        for (GameEffect txt : text) {
            if (!txt.isOver())
                txt.draw(g2d);
        }
    }

    @Override
    public void run() {
        init();
        long startTime;
        long elapsed;
        long wait;
        while (running) {
            startTime = System.nanoTime();
            update();
            render();
            repaint();
            elapsed = System.nanoTime() - startTime;
            wait = targetTime - elapsed / 1000000;
            if (wait > 0) {
                try {
                    Thread.sleep(wait);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void setRndApple() {
        apple.setPosition(rnd.nextInt(WIDTH), rnd.nextInt(HEIGHT));
    }

    private void setFPS(int fps) {
        targetTime = 1000 / fps;
    }

    private void resetLevel() {
        snake = new Snake(SIZE, WIDTH / 2, HEIGHT / 2);
        apple = new Entity(SIZE);
        setRndApple();
        score = 0;
        gameover = true;
        level = 1;
        setFPS(level * 10);
    }

    private void showMessages() {
        g2d.setFont(g2d.getFont().deriveFont(Font.BOLD, SIZE));
        g2d.setColor(Color.WHITE);
        if (gameover)
            g2d.drawString("PRESS ENTER!", MESSAGE_X, MESSAGE_Y);

        FontMetrics fm = g2d.getFontMetrics();
        g2d.drawString("SCORE: " + score, SIZE / 2, SIZE);
        String levelStr = level + " :LEVEL";
        g2d.drawString(levelStr, WIDTH - SIZE - fm.stringWidth(levelStr), SIZE);

    }

    public void start() {
        running = false;
        if (thread != null)
            while (thread.isAlive())
                ;
        thread = new Thread(this);
        thread.start();
    }

    private void update() {
        if (gameover)
            return;
        snake.move();
        if (snake.isCollision()) {
            text[TXT_GAMEOVER].start(10);
            gameover = true;
            return;
        }
        if (snake.isCollision(apple)) {
            score++;
            setRndApple();
            snake.grow();
            updateLevel();
        }
        snake.adaptBounds(WIDTH - SIZE, HEIGHT - SIZE);
    }

    private void updateLevel() {
        if (score % MAX_LEVEL == 0) {
            level = (++level) % MAX_LEVEL;
            setFPS(level * MAX_LEVEL);
        }
    }

}
```


```
package game;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Entity {
    private int x;
    private int y;
    public final int size;
    private Rectangle bound = null;

    public Entity(int size) {
        this.size = size;
        bound = new Rectangle(0, 0, size, size);
    }

    public Rectangle getBound() {
        bound.x = x;
        bound.y = y;
        return bound;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public boolean isCollsion(Entity o) {
        if (o == this)
            return false;
        return getBound().intersects(o.getBound());
    }

    public void move(int dx, int dy) {
        x += dx;
        y += dy;
    }

    public void render(Graphics2D g2d) {
        g2d.fillOval(x+1, y+1, size-2, size-2);
        //g2d.fillRect(x + 1, y + 1, size - 2, size - 2);
    }

    public void setPosition(int x, int y) {
        setX(x);
        setY(y);
    }

    public void setX(int x) {
        this.x = (x / size) * size;
    }

    public void setY(int y) {
        this.y = (y / size) * size;
    }

}
```


```
package game;

import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Iterator;

public class Snake {
    private ArrayList<Entity> snake = new ArrayList<Entity>();
    private int dx = 0;
    private int dy = 0;

    public enum DIRECTION {
        UP, DOWN, LEFT, RIGHT
    }

    public Snake(int size, int x, int y) {
        Entity head = new Entity(size);
        snake.add(head);
        head.setPosition(x, y);
        for (int i = 1; i < 3; i++) {
            Entity e = new Entity(size);
            e.setPosition(head.getX() + (i * size), head.getY());
            snake.add(e);
        }
    }

    public void adaptBounds(int width, int height) {
        Entity head = getHead();
        if (head.getX() < 0)
            head.setX(width);
        if (head.getY() < 0)
            head.setY(height);
        if (head.getX() > width)
            head.setX(0);
        if (head.getY() > height)
            head.setY(0);
    }

    private Entity getHead() {
        return snake.get(0);
    }

    public void grow() {
        Entity e = new Entity(getHead().size);
        e.setPosition(-100, -100);
        snake.add(e);
    }

    public boolean isCollision() {
        Entity head = getHead();
        for (Entity e : snake) {
            if (e.isCollsion(head))
                return true;
        }
        return false;
    }

    public boolean isCollision(Entity apple) {
        return getHead().isCollsion(apple);
    }

    public boolean isMoving() {
        return !(dx == 0 && dy == 0);
    }

    public void move() {
        if (dx == 0 && dy == 0)
            return;
        Entity head = getHead();
        for (int i = snake.size() - 1; i > 0; i--)
            snake.get(i).setPosition(snake.get(i - 1).getX(), snake.get(i - 1).getY());
        head.move(dx, dy);
    }

    public void render(Graphics2D g) {
        Iterator<Entity> it = snake.iterator();
        Entity head = it.next();
        g.setColor(Color.GREEN);
        while (it.hasNext())
            it.next().render(g);
        g.setColor(Color.BLUE);
        head.render(g);
    }

    public void setDirection(DIRECTION dir) {
        int speed = getHead().size;
        if (dir == DIRECTION.UP && dy == 0) {
            dy = -speed;
            dx = 0;
        }
        if (dir == DIRECTION.DOWN && dy == 0) {
            dy = speed;
            dx = 0;
        }
        if (dir == DIRECTION.LEFT && dx == 0) {
            dy = 0;
            dx = -speed;
        }
        if (dir == DIRECTION.RIGHT && dx == 0 && dy != 0) {
            dy = 0;
            dx = speed;
        }
    }
}
```


```
package game;

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

public abstract class GameEffect implements ActionListener {
    private Timer animator = null;
    protected int id = 0;
    private boolean over = true;
    private int limit = 0;
    protected int x;
    protected int y;

    public GameEffect(int x, int y, int limit) {
        this.x = x;
        this.y = y;
        this.limit = limit;
    }

    /**
     * Counts up member variable id. If a limit is reached, timer will be stopped
     * and over will be set to true.
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        if (id < limit)
            id++;
        else {
            animator.stop();
            over = true;
        }
    }

    /**
     * Do graphical stuff here.
     *
     * @param g
     */
    public abstract void draw(Graphics g);

    public boolean isOver() {
        return over;
    }

    public void start(int delay) {
        id = 0;
        if (animator == null)
            animator = new Timer(delay, this);
        else {
            animator.stop();
            animator.setDelay(delay);
        }
        over = false;
        animator.start();
    }

}
```


```
package game;

import java.awt.Graphics;
import java.awt.event.ActionEvent;

public class FallingText extends GameEffect {
    protected String text = null;
    protected int range = 20;
    protected double angle = 0;
    protected boolean down = true;

    public FallingText(String text, int x, int y, int limit, int range, boolean down) {
        super(x, y, limit);
        this.text = text;
        this.range = range;
        this.down = down;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        super.actionPerformed(e);
        if (!isOver())
            angle += .05;
    }

    @Override
    public void draw(Graphics g) {
        g.drawString(text, x + (int) (Math.cos(angle) * range), y + (down ? id : -id));
    }

}
```


```
package game;

import java.awt.FontMetrics;
import java.awt.Graphics;

public class SnakeText extends FallingText {

    public SnakeText(String text, int x, int y, int limit, int range) {
        super(text, x, y, limit, range, false);
    }

    @Override
    public void draw(Graphics g) {
        char[] c = text.toCharArray();
        FontMetrics fm = g.getFontMetrics();
        int offX = id;
        for (int i = 0; i < c.length; i++) {
            int offY = (int) (Math.cos(angle) * range) * (i % 2 == 0 ? 1 : -1);
            g.drawString(c[i] + "", x + offX, y + offY);
            offX += fm.stringWidth(" " + c[i]);
        }
    }
}
```

.


----------



## mihe7 (17. Jan 2020)

Blender3D hat gesagt.:


> Da gibt es natürlich auch noch Potential zur Verbesserung z.B aktives Rendern anstatt repaint()


Weil ich mit Spielen nichts am Hut habe: was ist denn aktives Rendern? Ist damit das Zeichnen auf dem Graphics-Kontext außerhalb von repaint() gemeint?


----------



## Blender3D (17. Jan 2020)

mihe7 hat gesagt.:


> Weil ich mit Spielen nichts am Hut habe: was ist denn aktives Rendern? Ist damit das Zeichnen auf dem Graphics-Kontext außerhalb von repaint() gemeint?


Ja. Das repaint() löst immer eine ganze Kette von Aktionen aus.
In Java besteht die Möglickeit mittels

```
private final GraphicsConfiguration gfxConf = GraphicsEnvironment.getLocalGraphicsEnvironment()
            .getDefaultScreenDevice().getDefaultConfiguration(); // used to create image for drawing
```
Die Grafikumgebung zu ermitteln.
daraus lässt sich ein Image kreieren

```
imageBuffer = gfxConf.createCompatibleImage(SCREEN_WIDTH, SCREEN_HEIGHT);
```

Die Zeichenmethode könnte man dann in etwa so formulieren


```
private void paintScreen() {
        Graphics g;
        try {
            g = this.getGraphics();
            if ((g != null) && (imageBuffer != null))
                g.drawImage(imageBuffer, 0, 0, null);
            // Sync the display on some systems.
            // (on Linux, this fixes event queue problems)
            Toolkit.getDefaultToolkit().sync();
            g.dispose();
        } catch (Exception e) { // quite commonly seen at applet destruction
            System.err.println("Graphics error: " + e);
        }
    }
```

Ein Buchtipp dazu:  https://www.amazon.de/Killer-Game-P...ame+programming+in+java&qid=1579263811&sr=8-2


----------



## Blender3D (17. Jan 2020)

Ein kleiner Breakout Klone den habe ich mit aktivem Rendering gemacht.



mihe7 hat gesagt.:


> aktives Rendern


----------

