Reputation: 53
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.
Upvotes: 1
Views: 1040
Reputation: 36423
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:
direction
is V, the snake is going to the leftsnakeMove
is called, the method evaluates direction
which is V so snake continues to move to the leftdirection
is set to up direction == "U"
direction
is set to right direction == "H"
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 tickedTo 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.add("H");
window.remove(restartButton);
score = 0;
snakeParts = 7;
newFruit();
repaint();
}
if (actionEvent.getSource() == toInstructionsButton && !instructions) {
instructions = true;
window.remove(toInstructionsButton);
repaint();
}
if (actionEvent.getSource() == tmMove) {
if (gameRunning) {
snakeMove();
checkFruit();
checkCollisions();
} else {
repaint();
}
repaint();
}
}
@Override
public void keyTyped(KeyEvent ke) {
}
@Override
public void keyPressed(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_SPACE && !gameRunning && instructions) {
snakeMove();
checkFruit();
checkCollisions();
newFruit();
gameRunning = true;
instructions = false;
}
if (ke.getKeyCode() == KeyEvent.VK_SPACE && gameRunning) {
if (gameStarted) {
gameStarted = false;
tmMove.stop();
} else {
tmMove.start();
gameStarted = true;
}
}
if (gameStarted) {
switch (ke.getKeyCode()) {
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_D:
directions.add("H");
break;
case KeyEvent.VK_LEFT:
case KeyEvent.VK_A:
directions.add("V");
break;
case KeyEvent.VK_UP:
case KeyEvent.VK_W:
directions.add("U");
break;
case KeyEvent.VK_DOWN:
case KeyEvent.VK_S:
directions.add("N");
break;
case KeyEvent.VK_E:
tmMove.setDelay(200);
break;
case KeyEvent.VK_M:
tmMove.setDelay(150);
break;
case KeyEvent.VK_H:
tmMove.setDelay(100);
break;
}
}
}
@Override
public void keyReleased(KeyEvent ke) {
}
}
Some other notes to name a few are:
SwingUtilities.invokeLater
setBounds
or setSize
, use an appropriate layout manager and call JFrame#pack()
after all components are added and before making it visiblegetPreferredSize
as opposed to calling setPreferredSize
JLabel
and add it to a JPanel
with an appropriate layoutGraphics
object to a Graphics2D
object and some anti-aliasing and other rendering hints to your drawing so it looks sharperKeyListener
actionPerformed
for all purposes such as timer and buttons callbacks, use anyomous classes to make the code more clean and conciseUpvotes: 1
Reputation: 840
Isn't this the right solution though?
If someone turns too fast, say when travelling left, do an 'up' and 'right' too fast, snake is still on the same y-axis, since its hasn't changed yet with up movement, and with a 'right' key press, snake is trying to move on x-axis while y-axis value is still the same.
I captured x and y values in your program and they look like this for a normal run at the first turn to down. After start, I let the snake run all the way to the right wall and turn just down before it hit the wall:
x[0],y[0] | x[1],y[1]
720 , 0 | 680, 0 <-- coordinates before turn
720 , 40 | 720, 0 <-- coordinates after turn
For this one, after start, I let the snake run to the wall and then did a fast turn to down + right, causing a game over, coordinates look like this:
x[0],y[0] | x[1],y[1]
720 , 0 | 680, 0 <-- coordinates before turn
680 , 0 | 720, 0 <-- coordinates after down + right turn
The behavior to me looks like what it should be, unless I've missed something. If you want to avoid this, try adding a small fraction of delay after a keypress.
Upvotes: 1