0

I would like to repaint my screen. As of now all it does is show the first screen with a dot where the head is supposed to be. This is fine, however I've written in my code that I want to move the head down 10 pixels every second. I'm printing at what point the head is supposed to be at, and in the command prompt it shows that the y value is indeed increasing. However on my screen the head is not moving.

I have tried using the revalidate method, trying to extend the canvas class instead of jframe, I have tried using different classes just for the paint method, i have tried replacing the paint method with the paintComponent method. And as you can probably tell I have a subpar understanding of anything related to painting in java. I have tried reading into these superclasses but they are too complicated for me to understand. I have also tried running without the sleep declaration. This didn't matter.

Main class: This class contains the main method to start running the snake game.

import java.util.concurrent.TimeUnit;

public class Main{

    public static void main(String[] args) throws InterruptedException {
        Main programma = new Main();
        programma.rungame();
    }

void rungame() throws InterruptedException {
        AllGUIElements gui = new AllGUIElements();
        gui.gui();
        while (true) {
            TimeUnit.SECONDS.sleep(1);
            gui.setGameScreen();
        }
    }
}

AllGUIElements class: This class makes a new frame, containing a new panel. This panel is being painted using paintComponent. setGameScreen updates the position of the head and is supposed to repaint the screen.

import javax.swing.*;
import java.awt.*;

public class AllGUIElements extends JPanel {

    private JFrame frame;
    private JPanel panel;

    private int screen;

    Snake hoofd = new Head(new Point(30,30),3,null);

    void gui() throws InterruptedException {

        frame = new JFrame("Snake Game");
        panel = new AllGUIElements();
        panel.setBackground(Color.GRAY);
        panel.setSize(1000,500);
        frame.setSize(1000,500);
        frame.add(panel);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

void setGameScreen() {
    repaint();
        if (hoofd.getDirection() == 1) {
            hoofd.setPosition(new Point(hoofd.getPosition().x, hoofd.getPosition().y-10));
        }
        if (hoofd.getDirection() == 2) {
            hoofd.setPosition(new Point(hoofd.getPosition().x+10, hoofd.getPosition().y));

        }
        if (hoofd.getDirection() == 3) {
            hoofd.setPosition(new Point(hoofd.getPosition().x, hoofd.getPosition().y+10));

        }
        if (hoofd.getDirection() == 4) {
            hoofd.setPosition(new Point(hoofd.getPosition().x-10, hoofd.getPosition().y));
        }
}

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
            g.setColor(Color.DARK_GRAY);
            g.fillRect(0, 0, 1000, 10);
            g.fillRect(0, 0, 10, 500);
            g.fillRect(990, 0, 10, 500);
            g.fillRect(0, 490, 1000, 10);

            g.setColor(Color.GREEN);
            g.fillRect(hoofd.getPosition().x, hoofd.getPosition().y, 10, 10);
    }

}

Screenobject class: Parent class of Snake and Food (which is not posted since it's not necessary) which returns the position of the head, bodypart and food.

import java.awt.*;

public class Screenobject{

    Point pos;

    Screenobject(Point pos){
        this.pos = pos;
    }

    void setPosition(Point pos){
        this.pos = pos;
    }

    Point getPosition() {
        return pos;
    }
}

Snake class: Child of Screenobject and parent of Head and Body. This class sets the direction of the objects.

import java.awt.*;

public class Snake extends Screenobject{

    int direction;
    //directions:
    //1 = up
    //2 = right
    //3 = down
    //4 = left

    Snake nextpart;

    Snake(Point pos, int direction, Snake nextpart){
        super(pos);
        this.direction = direction;
        this.nextpart = nextpart;
    }

    int getDirection() {
        return direction;
    }
}

Head class: Child class of snake and is supposed to be the first object of a linked list for the snake.

import java.awt.*;

public class Head extends Snake{
    Head(Point pos, int direction, Snake nextpart){
        super(pos, direction, nextpart);
    }
}

Body class: Child class of Snake and grows for every food eaten.

import java.awt.*;

public class Body extends Snake{
    Body(Point pos, int direction, Snake nextpart){
        super(pos, direction, nextpart);
    }

}
0963038
  • 33
  • 4
  • 1
    1) `while (true) {` **DON'T** use `while (true)` in a Swing program, you'll block the EDT. Use a `Swing Timer`. [For example](https://stackoverflow.com/questions/41839010/appletviewer-bugged-and-trying-to-involve-a-timer/41841566#41841566). 2) Don't override `paint(...)`, override `paintComponent(...)` instead, please check the tutorial on [how to perform custom-painting in Swing](https://docs.oracle.com/javase/tutorial/uiswing/painting/index.html) – Frakcool Jun 03 '19 at 21:05
  • 1
    3) Don't explicitly set `JPanel` size, override its `getPreferredSize(...)` method instead, see [this post](https://stackoverflow.com/q/7229226/2180785), 4) Give your variables meaningful names `f`, `p`, don't give any clue about what they are or do. Also use `camelCase` on your variables and methods, so `setgamescreen()` becomes `setGameScreen()` – Frakcool Jun 03 '19 at 21:07
  • we don't know where `gui` is being called, probably from within the EDT (Event Dispatch Thread) which is also responsible for updating (painting) the GUI. As long as your method does not return, the EDT is unable to `paint`; and `repaint` does not call `paint` it just marks/queues the component for later painting... – user85421 Jun 03 '19 at 21:10
  • Yeah I've noticed there were a lot of things wrong with it after I read your comment. I've tidied it up a bit and tried to place the while true in main, I'm currently still reading your suggestion about Swing Timer. – 0963038 Jun 03 '19 at 22:08
  • 1
    1) *"your comment"* Tip: Add @Frakcool (or whoever, the `@` is important) to *notify* the person of a new comment. 2) Next [edit], post a [mre]. – Andrew Thompson Jun 04 '19 at 02:56
  • 1
    2) (continued) Note that `Snake`, `Body` & `Head` are missing, so people trying to help are still unable to run the code to test ideas & check details. – Andrew Thompson Jun 04 '19 at 03:03
  • @Frakcool I have added the remaining classes. – 0963038 Jun 04 '19 at 22:29
  • @AndrewThompson I have added the remaining classes. – 0963038 Jun 04 '19 at 22:29
  • 1
    *"I have added the remaining classes."* Although I mentioned missing classes, it was not to get you to post your entire project. Please go back to the [mre] document and read it more carefully. – Andrew Thompson Jun 05 '19 at 02:35
  • 1
    I made this program [Gif of program working](https://i.stack.imgur.com/oDLqX.gif). It has less than 100 lines of code (it would be considered as an MRE); after you create an [mcve] as suggested by @AndrewThompson twice, only then I will post the code, and I have it on GitHub already ready to be posted. I know you're new to the site (not too new but we just want you to improve your future questions so that you can get faster and better answers) :) – Frakcool Jun 05 '19 at 04:05
  • @Frakcool I have updated the question again. I have used the "Divide and conquer" method to only post the necessary information. I didn't manage to make it any shorter without omitting code to make it run. The problem of course is still there. – 0963038 Jun 05 '19 at 12:10

1 Answers1

1

First things first:

I have used the "Divide and conquer" method to only post the necessary information

Yes, you posted only the necessary information, but you're still using too many classes for a MCVE / MRE, ideally all your code would fit in a single class.

I didn't manage to make it any shorter without omitting code to make it run.

That's the idea about us asking for an MRE, you should create a brand new program to isolate the problem, in your case this is: Move a shape on a certain direction and keep moving on that direction.

Right now, I can see that you've tried creating it, and to improve, so I will post an example of what is expected in future questions of your so that you ask better questions in the future.

That being said, let's go and answer your question.


This is an example with less than 100 lines of code (not including comments) that addresses the following issues in your code:

  1. Removes the while(true) statement in favor of a Swing Timer to prevent blocking the Event Dispatch Thread (EDT)

  2. Places the program on the EDT, see point #2 of this answer

  3. Makes use of JFrame#pack() method instead of manually setting it with setSize(...) for both the JFrame and JPanel

  4. Gets rid of the "magic numbers" for the directions and uses an enum for this matter, and thus the code is more readable as we all know that TOP should move it to top, but we don't know that 1 should move it to top.

  5. Makes use of the Shape API to draw the shapes on the JPanel as shown in this answer

  6. I would also suggest naming your methods using camelCase so that rungame() becomes runGame() as it's easier to read, same for other methods. And give them more meaningful names such as hoofd, I don't know what that is and if I read it alone without context it would be extremely difficult to say what type of object it is.


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class SnakeGame {
    private JFrame frame;
    private Snake snake;
    private JPanel buttonsPane;
    private JButton[] buttons; // Our array of buttons
    private Timer timer;
    private Direction currentDirection;

    // This enum will be used to determine the direction the snake will take.
    private enum Direction {
        TOP, LEFT, BOTTOM, RIGHT
    }

    public static void main(String[] args) {
        // We place our program on the EDT using Java 8 lambda expressions.
        SwingUtilities.invokeLater(() -> new SnakeGame().createAndShowGUI());
    }

    private void createAndShowGUI() {
        frame = new JFrame(getClass().getSimpleName());
        snake = new Snake();
        buttonsPane = new JPanel();
        buttons = new JButton[Direction.values().length];

        for (int i = 0; i < buttons.length; i++) {
            buttons[i] = new JButton(Direction.values()[i].toString()); // We create a JButton with the current value of the direction
            buttons[i].addActionListener(listener); // We set their ActionListeners
            buttonsPane.add(buttons[i]); // And add them to the buttonsPane
        }

        currentDirection = Direction.RIGHT; // We set a default direction
        timer = new Timer(1000, listener); // And start our Swing Timer

        // We add our components and then pack the frame, after that we start the timer.
        frame.add(snake);
        frame.add(buttonsPane, BorderLayout.SOUTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
        timer.start();
    }

    // Our ActionListener for moving the snake
    private ActionListener listener = e -> { // Using Java 8 lambda expressions again
        // We set the current direction using a ternary, if the source of the event is
        // the timer we leave the current direction as is
        // otherwise we set it to the direction from the button clicked
        currentDirection = e.getSource().equals(timer) ? currentDirection : Direction.valueOf(e.getActionCommand());
        snake.move(currentDirection); // And we call the move method
    };

    @SuppressWarnings("serial")
    class Snake extends JPanel {
        private int xPos;
        private int yPos;
        private static final int SPEED = 10; // We set the speed as a constant (10 pixels at a time) in any direction

        // We determine the movement direction
        public void move(Direction direction) {
            switch (direction) {
            case TOP:
                yPos -= SPEED;
                break;
            case LEFT:
                xPos -= SPEED;
                break;
            case BOTTOM:
                yPos += SPEED;
                break;
            case RIGHT:
                xPos += SPEED;
                break;
            }
            this.repaint(); // And repaint the snake
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(Color.DARK_GRAY);
            g2d.fill(new Rectangle2D.Double(xPos, yPos, 10, 10)); // We draw a rectangle using the Shape API
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(300, 300); // We set the preferredSize of the JPanel to 300 x 300
        }
    }
}

The above code produces the following result, for Java 7 and below or if you don't want to use lambda expressions or are too difficult for you, check the point #2 on this answer, it shows how to place the program on the EDT without lambda expressions and this answer shows how to write the ActionListener without them as well.

There you go, a simple runnable example, it's complete, it's verifiable, it's minimal and that's what we expect from you in future questions.

enter image description here

Frakcool
  • 10,915
  • 9
  • 50
  • 89
  • 1
    I will keep all of this in mind. Thank you for being patient with me and trying to help me make better questions. I am indeed still quite new to programming and this forum, so this was very helpful. It's true that I don't know stuff like lambda yet, but this makes it all the more useful for me to learn. I would like to play around with the code you provided first instead of just mindlessly copy pasting, as I think that's the best way to learn. Thank you very much! – 0963038 Jun 05 '19 at 19:10
  • 1
    Also the "hoofd" thing is because I was thinking in Dutch (literally meaning head), in the future I will remember to change variable names in English if I ever have a question again, as I can understand that it's quite confusing. – 0963038 Jun 05 '19 at 19:11
  • Oh that makes sense, I actually recommend you to write your code in English even if it's for learning purposes. My first language is Spanish but I write all my code in English. Happy coding :) Oracle has really good tutorials for Swing, so check them out if in doubt. @0963038 – Frakcool Jun 05 '19 at 19:17