Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
189 views
in Technique[技术] by (71.8m points)

java - Problem with snake colliding with itself (snake game)

I've got this problem with my snake game, it runs into itself when you for example are going left and press up and right fast, because i only told the game that i can't press right if it's going left, and therefor if I press up right before i press right it allows me to which makes the snake go into itself.

So when you run the program just press Next and press space and the game should start then when you are going left just press up and right quickly after that and see it for yourself. I'm not sure how to fix this unfortunatly since we've only learned Java for like 6 months and we only really learned the basics like if etc. If you have any questions I am answering quick.

question from:https://stackoverflow.com/questions/65892347/problem-with-snake-colliding-with-itself-snake-game

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I've made some changes to your code which now make it work correctly as you would expect.

Essentially the problem was that if 2 keys are pressed fast enough before the timer ticks and snakeMove gets called, you would overwrite the direction variable and thus a key will be "missed".

So imagine these steps take place:

  1. direction is V, the snake is going to the left
  2. Timer ticks and snakeMove is called, the method evaluates direction which is V so snake continues to move to the left
  3. Before the timer ticks again I press up + right at the "same time". Thus 2 events take place before the timer ticks again:
    1. 1st key is processed so direction is set to up direction == "U"
    2. 2nd key is processed so direction is set to right direction == "H"
  4. Now only the timer ticks again and snakeMove is called. The method evaluates direction in a switch statement and direction == "H", thus we have "missed" the direction == "U"as it was overwritten by the second key press in keyPressed method the before the timer ticked

To overcome this as I suggested in your previous question to use a FIFO (first in first out) list to correctly process all keys so they are never "missed".

This can be done using a LinkedList which has the pop() function we need.

So in your code I renamed the global variable direction to currentDirection:

private String currentDirection; 

and removed the static modifier as this is not needed, I also removed the static modifier on snakeMove as again this is not needed and stops us from accessing instance variables i.e. currentDirection. I also changed the scope to private as in the snippet you showed it was unnecessary for it to be public, but these changes are just for more correctness of code. I then created a global variable:

private LinkedList<String> directions = new LinkedList<>();

Then everywhere (except in the snakeMove method) I removed currentDirection = and replaced it with directions.add(...), thus we are no longer changing a single variable, but rather adding each direction to our FIFO/LinkedList. I also removed some if checks you did in the keyPressed as this was not needed, even if the snake is going in the same direction as the key that is pressed - who cares, just add it to the list of keys pressed so we can process it later in snakeMove

Then in your snakeMove I did this:

public void snakeMove() {
    ...

    if (!directions.isEmpty()) { // if its not empty we have a new key(s) to process
        // proccess the keys pressed from the oldest to the newest and set the new direction
        currentDirection = directions.pop(); // takes the first oldest key from the queue
    }

    switch (currentDirection) {
        ...
    }
}

and that solves the problem mentioned above.

Here is the code with these changes implemented:


import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.util.LinkedList;

public class FirstGraphic extends JPanel implements ActionListener, KeyListener {

    //Storlek p? f?nstret
    static int WIDTH = 800, HEIGHT = 840;

    Timer tmMove = new Timer(150, this);

    private JFrame window;

    static int bodySize = 40, xNormalFruit = 0, yNormalFruit = 0, gameSquares = (WIDTH * HEIGHT) / bodySize,
            snakeParts = 7, score = 0, restartButtonWIDTH = 190, restartButtonHEIGHT = 50;

    static int x[] = new int[gameSquares];
    static int y[] = new int[gameSquares];

    private String currentDirection;
    boolean gameRunning = false, gameStarted = false, instructions = false, isDead = false;

    public static JButton restartButton = new JButton("STARTA OM"), toInstructionsButton = new JButton("N?sta");

    private LinkedList<String> directions = new LinkedList<>();

    public static void main(String[] args) {
        JFrame window = new JFrame("Snake Game");
        FirstGraphic content = new FirstGraphic(window);
        window.setContentPane(content);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setResizable(false);
        window.pack();
        restartButton.setBounds((WIDTH / 2) - (restartButtonWIDTH) / 2, (HEIGHT - 40) / 2 + 100, restartButtonWIDTH, restartButtonHEIGHT);
        restartButton.setBackground(new Color(48, 165, 55));
        restartButton.setFont(new Font("Arial", Font.BOLD, 20));
        window.setLocationRelativeTo(null);
        window.setVisible(true);
        content.setUp();
    }

    public FirstGraphic(JFrame window) {
        super();
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setFocusable(true);
        requestFocus();
        this.window = window;
    }

    public void setUp() {
        addKeyListener(this);
        setFocusable(true);
        setFocusTraversalKeysEnabled(false);
        restartButton.addActionListener(this);
        directions.add("H");
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (gameRunning) {
            g.setColor(new Color(0, 0, 0));
            g.fillRect(0, 0, WIDTH, (HEIGHT - 40));
            g.setColor(new Color(63, 116, 41, 255));
            g.fillRect(0, HEIGHT - 40, WIDTH, (2));
            g.setColor(new Color(0, 0, 0, 240));
            g.fillRect(0, HEIGHT - 38, WIDTH, 38);
            g.setColor(new Color(0, 0, 0));
        }
        draw(g);
    }

    public void draw(Graphics g) {
        if (gameRunning) {
            g.setColor(new Color(35, 179, 52, 223));
            g.fillOval(xNormalFruit, yNormalFruit, bodySize, bodySize);
            g.setColor(new Color(44, 141, 23, 255));
            g.setFont(new Font("Arial", Font.BOLD, 18));
            for (int i = 0; i < snakeParts; i++) {
                if (i == 0) {
                    g.setColor(Color.RED);
                    g.fillOval(x[i], y[i], bodySize, bodySize);
                } else {
                    g.setColor(Color.PINK);
                    g.fillOval(x[i], y[i], bodySize, bodySize);
                }
            }
        } else if (!gameRunning && gameStarted) {
            gameOver(g);
        } else if (!instructions) {
            startScene(g);
        } else {
            instructions(g);
        }
    }

    public void startScene(Graphics g) {
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, WIDTH, HEIGHT);
        g.setColor(Color.WHITE);
        g.setFont(new Font("Arial", Font.BOLD, 85));
        g.drawString("Ormen Olle's", 150, 170);
        g.drawString("?ventyr", 235, 254);
        window.add(toInstructionsButton);
        toInstructionsButton.setBounds(240, 660, 300, 100);
        toInstructionsButton.setBackground(new Color(48, 165, 55));
        toInstructionsButton.setForeground(Color.BLACK);
        toInstructionsButton.setFont(new Font("Arial", Font.BOLD, 60));
        toInstructionsButton.addActionListener(this);
    }

    public void instructions(Graphics g) {
        g.setFont(new Font("Arial", Font.BOLD, 85));
        g.setColor(new Color(14, 69, 114));
        g.drawString("PRESS SPACE", 210, 720);

    }

    public void gameOver(Graphics g) {
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, WIDTH, HEIGHT);
        g.setColor(Color.red);
        g.setFont(new Font("Arial", Font.BOLD, 65));
        FontMetrics metrics = getFontMetrics(g.getFont());
        g.drawString("Du dog!", (WIDTH - metrics.stringWidth("Du dog!")) / 2, (HEIGHT - 40) / 2);
        g.setColor(new Color(44, 141, 23, 255));
        g.setFont(new Font("Arial", Font.BOLD, 20));
        FontMetrics metrics2 = getFontMetrics(g.getFont());
        g.drawString("SCORE: " + score, (WIDTH - metrics2.stringWidth("SCORE:  " + score)) / 2, 50);
        window.add(restartButton);
    }

    public void checkFruit() {
        if ((x[0] == xNormalFruit) && (y[0] == yNormalFruit)) {
            snakeParts++;
            score++;
            newFruit();
        }
        for (int v = 1; v < snakeParts; v++) {
            if ((x[v] == xNormalFruit) && y[v] == yNormalFruit) {
                newFruit();
            }
        }
    }

    public void checkCollisions() {
        for (int i = snakeParts; i > 0; i--) {
            if ((x[0] == x[i]) && (y[0] == y[i])) {
                gameRunning = false;
                isDead = true;
            }
        }
        if (x[0] < 0) {
            gameRunning = false;
            isDead = true;
        }
        if (x[0] == WIDTH) {
            gameRunning = false;
            isDead = true;
        }
        if (y[0] < 0) {
            gameRunning = false;
            isDead = true;
        }
        if (y[0] > (HEIGHT - 40) - bodySize) {
            gameRunning = false;
            isDead = true;
        }
        if (!gameRunning) {
            tmMove.stop();
        }
    }

    public void snakeMove() {
        for (int i = snakeParts; i > 0; i--) {
            x[i] = x[i - 1];
            y[i] = y[i - 1];
        }

        if (!directions.isEmpty()) {
            currentDirection = directions.pop();
        }

        switch (currentDirection) {
            case "H":
                x[0] = x[0] + bodySize;
                break;
            case "V":
                x[0] = x[0] - bodySize;
                break;
            case "U":
                y[0] = y[0] - bodySize;
                break;
            case "N":
                y[0] = y[0] + bodySize;
                break;
        }
    }

    public static void newFruit() {
        xNormalFruit = (rollDice(WIDTH / bodySize) * bodySize) - bodySize;
        yNormalFruit = (rollDice((HEIGHT - 40) / bodySize) * bodySize) - bodySize;
    }

    public static int rollDice(int numberOfSides) {
        //Kastar en t?rning med ett specifikt antal sidor.
        return (int) (Math.random() * numberOfSides + 1);
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {
        if (actionEvent.getSource() == restartButton && isDead) {
            isDead = false;
            for (int i = 0; i < snakeParts; i++) {
                if (i == 0) {
                    x[i] = 0;
                    y[i] = 0;
                } else {
                    x[i] = 0 - bodySize;
                    y[i] = 0;
                }
            }
            gameRunning = true;
            tmMove.start();
            //direction = "H";
            directions.clear();
            directions.a

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...