I’m trying to write a snake game, but the KeyListener does not work

I had tried to add the listener to the object of the Snake class , to the object of the Window class and object of JFrame class I created in the constructor of the Snake class, but it still does not work. Сharacters that must be written and the snake must turn, if i press any key, but it doesn’t happen. Сan it be because of the snakeThread?

public class Main {

    public static void main(String[] args) {
        Window window = new Window();
        System.out.print("k");
    }
}
import javax.swing.*;
import java.awt.*;

public class Window  extends JPanel {
    private Snake snake; 


    public Window() {
        super(true);
        snake = Snake.getSnake(50, 50);
        Thread snakeThread = new Thread(snake);
        snakeThread.start();
     

    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        snake.paint(g);
    }
}
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;


public class Snake extends JPanel implements Runnable {

    public static int length;
    public static ArrayList<Point> parts;
    public static Field field;
    public static Food food;
    public static int speedX=0;
    public static int speedY=1;

private static Snake snake = null;

    public static final int TIME_DELTA = 1000;
public static  Snake getSnake(int w, int h)
{
    if(snake == null) {
        snake = new Snake(w, h);
        snake.addKeyListener(new Turn(snake));
    }
    return snake;
}

    private Snake(int Width, int Heigth) {

        JFrame jf = new JFrame();
        jf.setSize(500,500);
        jf.add(this); //this - это Jpanel которым расширяется Snake
        jf.setVisible(true);


        food = new Food();field = Field.getField();
        Point start = new Point((int)Width/2, (int)Heigth/2); //размеры поля, а не окна
        parts = new ArrayList<>();parts.add(start);
        Point p1 = new Point((int)start.getX(), ((int)start.getY())-1);parts.add(p1);
        Point p2 = new Point((int)start.getX(), ((int)p1.getY())-1);
        parts.add(p2);length = 3;
    }
  

    private boolean checkHead()
    {
        for (int i=1; i<parts.size(); ++i)
        {
            if(parts.get(parts.size()-1).getLocation() == parts.get(i).getLocation())
                return false;
        }

        if(parts.get(parts.size()-1).getX() <=0 || parts.get(parts.size()-1).getX() >= field.sizeX ||
                parts.get(parts.size()-1).getY() <=0 || parts.get(parts.size()-1).getY() >= field.sizeY )
            return false;

        return true;
    }

    public static void move()
    {
        for (Point i: parts)
        {
            i.y=i.y-1*speedY;
            i.x-=1*speedX;
        }
    }
    public static void eat()
    {
        Point np = new Point ((int)parts.get(length).getX(),(int)parts.get(length).getY()-1 );
        parts.add(np);
        ++length;
        food.respawn();
    }

    public static boolean checkFood()
    {
        if(parts.get(parts.size()-1).getX() == food.x &&  parts.get(parts.size()-1).getY()==food.y)
            return true;
        else
            return false;
    }

  
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        for (Point i: parts) 
            g.fillRect((int) i.getX() * 10, (int) i.getY() * 10, 8, 8);

       g.setColor(Color.RED);
       g.fillRect(food.x * 10, food.y * 10, 8, 8);
       g.setColor(Color.BLACK);
    }

    @Override

            public void run() {
        while (checkHead()) {
            move();
            repaint();
            if(checkFood())
                eat();
            try {
                Thread.sleep(TIME_DELTA);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
            }

            public  void turn(char Key)
            {
                int delta = 0;
                for(Point i: parts)
                {
                    switch (Key) {
                        case 'a':
                            i.y=parts.get(parts.size()-1).y;
                            i.x=parts.get(parts.size()-1).x+delta;
                            break;
                        case'd':
                            i.y=parts.get(parts.size()-1).y;
                            i.x=parts.get(parts.size()-1).x-delta;
                            break;

                        case 'w':
                            i.x=parts.get(parts.size()-1).x;
                            i.y=parts.get(parts.size()-1).y-delta;
                            break;
                        case's':
                            i.x=parts.get(parts.size()-1).x;
                            i.y=parts.get(parts.size()-1).y+delta;
                            break;
                    }
                    ++delta;
                }
                repaint();
            }


}
import javax.swing.*;
import java.awt.*;
import java.util.Random;

public class Food extends JPanel {
    public static  int x;
    public static int y;
    private static Random random;

    public Food()
    {
        super(true);
        random = new Random();
        
       x =  random.nextInt(50);
       y = random.nextInt(50);
    }

    @Override
   public void paint(Graphics g) {
        super.paint(g);
        g.setColor(Color.RED);
        g.fillRect(x * 10, y * 10, 8, 8);
        g.setColor(Color.BLACK);
    }

    public  void respawn()
    {
        x = random.nextInt(40);
        y = random.nextInt(40);
        repaint();
    }
}

The listener is here:

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;


public class Turn implements KeyListener {
   private char key = 'O';
   private Snake snake;

   public Turn(Snake s)
   {
       this.snake = s;
   }


    @Override
    public void keyTyped(KeyEvent e) {
        System.out.println("0");//when I press a button, "0" must be written, but it doesn't
        if(e.getKeyChar() == 'A')
        {
            System.out.println("a");//this character too
            if(snake.speedX==0)
            {
                snake.speedX=-1;//speedX was not changed
                snake.speedY=0;
                key='a';
            }
        }
        else if (e.getKeyChar() == 'W')
        {
            System.out.println("w");
            if(snake.speedY==0)
            {
                snake.speedY=-1;
                snake.speedX=0;
                key='w';
            }

        }
        else if (e.getKeyChar() == 'S')
        {
            System.out.println("s");
            if(snake.speedY==0)
            {
                snake.speedY=1;
                snake.speedX=0;
                key='s';
            }

        }
        else if (e.getKeyChar() == 'D')
        {
            System.out.println("d");
            if(snake.speedX==0)
            {
                snake.speedX=1;
                snake.speedY=0;
                key='d';
            }
        }
        if(key!='O')
snake.turn(key);
    }

    @Override
    public void keyPressed(KeyEvent e) {

    }

    @Override
    public void keyReleased(KeyEvent e) {

    }
}

Answer

I agree with everything in the answer from camickr. There are many things wrong in your code, and starting from scratch with those things in mind will help avoid many of the issues you are facing.

However, your actual issue is this line private static Snake snake = null; and this method:

public static  Snake getSnake(int w, int h)
{
    if(snake == null) {
        snake = new Snake(w, h);
        snake.addKeyListener(new Turn(snake));
    }
    return snake;
}

You should NEVER need to create a self reference inside a class like that. The Snake class is already a Snake object, so using private static Snake snake = null; inside the class simply creates a new reference to a completely new and different Snake within your Snake. Instead, you need to use the this keyword to refer to the Snake object, for example, this.addKeyListener(new Turn(this)); instead of snake.addKeyListener(new Turn(snake));

Delete/remove the getSnake method, you are trying to mix static and non static objects and it is not needed, we can move the keylistener part of the method to our constructor (see below). Note that if you want to update the width and height of your snake then you should make a different method that updates the parts and p1 and p2 separately.

Fixing the Snake class:

The fixed Snake class might look something like this:

public class Snake extends JPanel implements Runnable {

    //None of the variables or methods should be static
    //remove the static keyword from everywhere in the Snake class
    public int length;
    public ArrayList<Point> parts;
    public Field field;
    public Food food;
    public int speedX=0;
    public int speedY=1;

    public final int TIME_DELTA = 1000;

    //The constructor needs to be a public method (Not private)
    public Snake(int Width, int Heigth) {
        //Delete all the JFrame code,
        //it should be done in the main method

        //Add the key ilstener here (moved from the getSnake method)
        this.addKeyListener(new Turn(this));
        
        //Create the snake
        food = new Food();
        field = Field.getField();
        Point start = new Point((int)Width/2, (int)Heigth/2); //размеры поля, а не окна
        parts = new ArrayList<>();
        parts.add(start);
        Point p1 = new Point((int)start.getX(), ((int)start.getY())-1);parts.add(p1);
        Point p2 = new Point((int)start.getX(), ((int)p1.getY())-1);
        parts.add(p2);
        length = 3;
    }

    //Rest of the code removed for clarity
    ...
}

Fix the main method and delete the Window class:

To use this updated Snake class you can completely delete the Window class and use something like this in your main class:

public class Main {

    public static void main(String[] args) {
        //Create JFrame
        JFrame jf = new JFrame();
        jf.setSize(500,500);

        //Create Snake
        Snake snake = new Snake(50, 50);

        //Add Snake to JFrame and set the frame visible
        jf.add(snake);
        jf.setVisible(true);
        
        //Finally start the Snake thread
        Thread snakeThread = new Thread(snake);
        snakeThread.start();
    }
}

There are still a bunch of things that need fixing, but this should solve your key listener not working.


Other fixes:

Instead of key listeners a better solution is to use key bindings. We can create a key binding like this in you main method:

//Key binding to trigger when KeyEvent.VK_W ('w') is pressed
snake.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), "NORTH");
snake.getActionMap().put("NORTH", new AbstractAction()
     {
     @Override
     public void actionPerformed(ActionEvent e)
     {
         //Your code here to change the snake direction
         //we can call the turn method from inside this snake object/class
         snake.turn('w');
     }
     });

You should override protected void paintComponent(Graphics g) instead of public void paint(Graphics g), inside your Snake class like this:

@Override
protected void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    for (Point i: parts) 
        g2d.fillRect((int) i.getX() * 10, (int) i.getY() * 10, 8, 8);

   g2d.setColor(Color.RED);
   g2d.fillRect(food.x * 10, food.y * 10, 8, 8);
   g2d.setColor(Color.BLACK);
}

Lastly, instead of making your Snake class implement Runniable you should create a simple stand alone swing timer something like this inside your Snake class:

void startTimer(){
    //Start timer to update snake location every 100ms
    Timer timer = new Timer(100, new ActionListener(){
        @Override
        public void actionPerformed(ActionEvent ae){
            //Your code here to update and repaint the snake location
            snake.updateLocation();
        }
    });
    timer.start();
}

And in your main method you can simple use snake.startTimer(); instead of Thread snakeThread = new Thread(snake); and snakeThread.start();