Search code examples
javaswingpaintcomponentmaze

How to update the graphics


How do you update graphics everytime you use wasd keys? Right now my player cant move and i want to update the grid everytime the player moves using one of those keys. How do i implement and update each time player when ever the arrow keys are being pressed. I have made the class but it doesnt seem to move whenever the keys are pressed.How can i implement movement and update the grid each time a key is being pressed. I did the code but the player doesnt seem to move.Can anyone tell me what i am doing wrong?

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;  // Needed for ActionListener
import javax.swing.event.*;  // Needed for ActionListener
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.Timer;
import java.util.Scanner;

class www extends JFrame
{
    static Maze maze = new Maze ();
    static Timer t;

    //======================================================== constructor
    public www ()
    {
        // 1... Create/initialize components
        // 2... Create content pane, set layout
        JPanel panel = new JPanel ();
        JPanel content = new JPanel ();        // Create a content pane
        content.setLayout (new BorderLayout ()); // Use BorderLayout for panel
        JPanel north = new JPanel ();
        north.setLayout (new FlowLayout ()); // Use FlowLayout for input area

        DrawArea board = new DrawArea (500, 500);
        // 3... Add the components to the input area.

        content.add (north, "North"); // Input area
        content.add (board, "South"); // Output area

        // 4... Set this window's attributes.
        setContentPane (content);
        pack ();
        setTitle ("MAZE");
        setSize (510, 570);
        setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo (null);           // Center window.
    }

    public static void main (String[] args)
    {

        www window = new www ();
        window.setVisible (true);

    }


    class DrawArea extends JPanel
    {
        public DrawArea (int width, int height)
        {
            this.setPreferredSize (new Dimension (width, height)); // size
        }

        public void paintComponent (Graphics g)
        {
            maze.show (g);
            // display current state of colony
        }
    }
}

class Maze 
{
    static Scanner sc;
    private int maze [][];

    public Maze ()
    {
        int [][] grid = 
            {{1,2,1,1,1,1,1,1,1,1,1,1,1},
                {1,0,1,0,1,0,1,0,0,0,0,0,1},
                {1,0,1,0,0,0,1,0,1,1,1,0,1},
                {1,0,0,0,1,1,1,0,0,0,0,0,1},
                {1,0,1,0,0,0,0,0,1,1,1,0,1},
                {1,0,1,0,1,1,1,0,1,0,0,0,1},
                {1,0,1,0,1,0,0,0,1,1,1,0,1},
                {1,0,1,0,1,1,1,0,1,0,1,0,1},
                {1,0,0,0,0,0,0,0,0,0,1,0,1},
                {1,1,1,1,1,1,1,1,1,1,1,0,1}};

        maze = grid;

    }


    public void keyPressed(KeyEvent e, Graphics g)
    {
        int x =  0, y = 0,velX = 0, velY = 0;
        int c =  e.getKeyCode();
        if(c == KeyEvent.VK_LEFT || c == KeyEvent.VK_A)
        {
            velX = -1;
            velY = 0;
            for (int row = 0 ; row < maze.length ; row++)
                for (int col = 0 ; col < maze [0].length ; col++)
                {
                    if (maze [row] [col] == 1) // life
                    {   
                        g.setColor (Color.black);
                    }
                    else if(maze [row][col] == 2)
                    {

                        g.setColor (Color.red);
                    }
                    else 
                    {
                        g.setColor(Color.white);
                    }
                    g.fillRect (col * 20 + 20, row * 20 + 20, 50, 50); // draw life form
            }

        }
        if(c == KeyEvent.VK_UP || c == KeyEvent.VK_W)
        {
            velX = 0;
            velY = -1;
            for (int row = 0 ; row < maze.length ; row++)
                for (int col = 0 ; col < maze [0].length ; col++)
                {
                    if (maze [row] [col] == 1) // life
                    {   
                        g.setColor (Color.black);
                    }
                    else if(maze [row][col] == 2)
                    {

                        g.setColor (Color.red);
                    }
                    else 
                    {
                        g.setColor(Color.white);
                    }
                    g.fillRect (col * 20 + 20, row * 20 + 20, 50, 50); // draw life form
            }
        }
        if(c == KeyEvent.VK_RIGHT || c == KeyEvent.VK_D)
        {
            velX = 1; 
            velY = 0;
            for (int row = 0 ; row < maze.length ; row++)
                for (int col = 0 ; col < maze [0].length ; col++)
                {
                    if (maze [row] [col] == 1) // life
                    {   
                        g.setColor (Color.black);
                    }
                    else if(maze [row][col] == 2)
                    {

                        g.setColor (Color.red);
                    }
                    else 
                    {
                        g.setColor(Color.white);
                    }
                    g.fillRect (col * 20 + 20, row * 20 + 20, 50, 50); // draw life form
            }

        }
        if(c == KeyEvent.VK_DOWN || c == KeyEvent.VK_S)
        {
            velX = 0;
            velY = 1;
            for (int row = 0 ; row < maze.length ; row++)
                for (int col = 0 ; col < maze [0].length ; col++)
                {
                    if (maze [row] [col] == 1) // life
                    {   
                        g.setColor (Color.black);
                    }
                    else if(maze [row][col] == 2)
                    {

                        g.setColor (Color.red);
                    }
                    else 
                    {
                        g.setColor(Color.white);
                    }
                    g.fillRect (col * 20 + 20, row * 20 + 20, 50, 50); // draw life form
            }

        }

    }

    public void keyTyped(KeyEvent e){}

    public void keyReleased(KeyEvent e){}



    public void show (Graphics g)
    {
        for (int row = 0 ; row < maze.length ; row++)
            for (int col = 0 ; col < maze [0].length ; col++)
            {
                if (maze [row] [col] == 1) // life
                {   
                    g.setColor (Color.black);
                }
                else if(maze [row][col] == 2)
                {

                    g.setColor (Color.red);
                }
                else 
                {
                    g.setColor(Color.white);
                }
                g.fillRect (col * 20 + 20, row * 20 + 20, 50, 50); // draw life form
        }

    }


    public class player extends JPanel implements ActionListener,KeyListener
    {

        Timer tm = new Timer(5,this);
        int x =  0, y = 0,velX = 0, velY = 0;
        public player ()
        {
            tm.start ();    
            addKeyListener(this);
            setFocusable(true);
            setFocusTraversalKeysEnabled(false);
        }

        public void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            g.setColor(Color.RED);
            g.fillRect(x,y,50,30);

        }

        public void actionPerformed(ActionEvent e)
        {
            x = x+velX;
            y = y +velY;
            repaint();
        }

        public void keyPressed(KeyEvent e)
        {
            int c =  e.getKeyCode();
            if(c == KeyEvent.VK_LEFT || c == KeyEvent.VK_A)
            {
                velX = -1;
                velY = 0;

            }
            if(c == KeyEvent.VK_UP || c == KeyEvent.VK_W)
            {
                velX = 0;
                velY = -1;
            }
            if(c == KeyEvent.VK_RIGHT || c == KeyEvent.VK_D)
            {
                velX = 1; 
                velY = 0;
            }
            if(c == KeyEvent.VK_DOWN || c == KeyEvent.VK_S)
            {
                velX = 0;
                velY = 1;
        }

    }

    public void keyTyped(KeyEvent e){}

    public void keyReleased(KeyEvent e){}

Solution

  • Okay, let's start with some of the problems...

    class www extends JFrame
    

    First, you should avoid overriding JFrame, you're not adding any new functionality to the class, locking yourself into a single use case and running the risk of other issues which make it hard to diagnose.

    Your class name really should start with a capital letter. Have a read through Code Conventions for the Java TM Programming Language, it will make it easier for people to read your code and for you to read others.

    Next...

    static Maze maze = new Maze ();
    static Timer t;
    

    static is a good sign of a bad design. You should avoid using them as a cross communication mechanism as you lose control over how their references are changed, making it difficult to diagnose possible problems

    Next...

    class DrawArea extends JPanel
    {
        public DrawArea (int width, int height)
        {
            this.setPreferredSize (new Dimension (width, height)); // size
        }
    
        public void paintComponent (Graphics g)
        {
            maze.show (g);
            // display current state of colony
        }
    }
    

    First, you're breaking the paint chain, which could cause no end of painting artifacts, you are obligated to call the paint methods super method before performing any custom painting. See Painting in AWT and Swing and Performing Custom Painting for more details

    Also, it's not the maze's responsibility to paint itself, it's up to the view to determine how best it should represent the model/maze

    Next...

    public class player extends JPanel implements ActionListener,KeyListener
    {
        //...
    }
    

    Okay, this class is simply never used, but it is the only time you register a KeyListener. You've also over complicating the process, as the DrawArea should also be responsible for painting the player and dealing with the user input.

    As a general rule of thumb, KeyListener is not an appropriate API, for a number of reasons. You're better of using the Key Bindings API, which solves the problems with KeyListener and makes it eaiser to change and implement other input devices.

    Your code generally doesn't focus on isolated responsibility, why is player allowed to change the position of the player?

    One of the key aspects of OOP is about isolating responsibility, this makes it easier to change things without adversely affecting the rest of the system, this is also support by the Model-View-Controller paradigm.

    The basic idea is, your model controls the data, maintains the state and defines the rules by which it can be changed (and could provide event notification as required). The view is responsible for rendering the state of the model. The controller is the glue which binds it together.

    Generally in a MVC, the model and view never communicate with each, it's the controller's responsibility to act as a conduit between them. This can be achieved directly (make calls against the controller directly) or more commonly through the use of an Observer Pattern, where the view and model generate events which the controller responds to.

    The first thing we need to do is define the basic functionality of each element, the information and functionality that each layer is will to expose to other parties, for example...

    public interface GameModel {
        public int[][] getMaze();
        public int[] getPlayerLocation();
        public void setPlayerLocation(int[] location);
        public void update(Set<Direction> directions);
    }
    
    public interface GameController {
        public int[][] getMaze();
        public int[] getPlayerLocation();
        public void setDirectionPressed(Direction direction, boolean pressed);
        public void start();
    }
    
    public interface GameView {
        public void update();
        public void setController(GameController controller);
        public GameController getController();
    }
    

    This is the beginning of the idea of Composition over inheritance which allows you to better decouple your code, as you're not relying on the physical implementation of the classes, but simply their aggreement to maintain a given contract.

    Next, we define the implementations....

    public class DefaultGameModel implements GameModel {
    
        private int[][] maze
                = {{1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                {1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1},
                {1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1},
                {1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1},
                {1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1},
                {1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1},
                {1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1},
                {1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1},
                {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1}};
    
        private int[] playerLocation = new int[]{1, 0};
    
        @Override
        public int[][] getMaze() {
            return maze;
        }
    
        @Override
        public int[] getPlayerLocation() {
            return playerLocation;
        }
    
        @Override
        public void setPlayerLocation(int[] playerLocation) {
            this.playerLocation = playerLocation;
        }
    
        @Override
        public void update(Set<Direction> directions) {
            int[] location = getPlayerLocation();
            int[][] maze = getMaze();
            int x = location[0];
            int y = location[1];
            if (directions.contains(Direction.UP)) {
                y--;
            } else if (directions.contains(Direction.DOWN)) {
                y++;
            }
            if (directions.contains(Direction.LEFT)) {
                x--;
            } else if (directions.contains(Direction.RIGHT)) {
                x++;
            }
            if (x < 0) {
                x = 0;
            } else if (x >= maze[0].length) {
                x = maze[0].length - 1;
            }
            if (y < 0) {
                y = 0;
            } else if (y >= maze.length) {
                y = maze.length - 1;
            }
    
            if (maze[y][x] == 0) {
                location = new int[]{x, y};
                setPlayerLocation(location);
            }
    
        }
    
    }
    
    public class DefaultGameController implements GameController {
    
        private GameView view;
        private GameModel model;
    
        private Timer timer;
    
        private Set<Direction> directions;
    
        public DefaultGameController(GameView view, GameModel model) {
            this.view = view;
            this.model = model;
            directions = new HashSet<>(4);
            view.setController(this);
        }
    
        public GameView getView() {
            return view;
        }
    
        public GameModel getModel() {
            return model;
        }
    
        @Override
        public int[][] getMaze() {
            return getModel().getMaze();
        }
    
        @Override
        public int[] getPlayerLocation() {
            return getModel().getPlayerLocation();
        }
    
        @Override
        public void setDirectionPressed(Direction direction, boolean pressed) {
            if (pressed) {
                directions.add(direction);
            } else {
                directions.remove(direction);
            }
        }
    
        @Override
        public void start() {
            // This isn't really required for this type of simple example, but what
            // does do is demonstrates at least one possible solution for simple
            // game loop
            //
            // Because of the basic nature of the game, it would be possible to have
            // setDirectionPressed call model.update and view.update
            if (timer != null && timer.isRunning()) {
                timer.stop();
            }
            timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    getModel().update(Collections.unmodifiableSet(directions));
                    getView().update();
                }
            });
            timer.start();
        }
    
    }
    
    public class DefaultGameView extends JPanel implements GameView {
    
        private GameController controller;
    
        public DefaultGameView() {
            addKeyBinding("left.pressed", KeyEvent.VK_LEFT, true, new MoveAction(Direction.LEFT, true));
            addKeyBinding("left.released", KeyEvent.VK_LEFT, false, new MoveAction(Direction.LEFT, false));
            addKeyBinding("right.pressed", KeyEvent.VK_RIGHT, true, new MoveAction(Direction.RIGHT, true));
            addKeyBinding("right.released", KeyEvent.VK_RIGHT, false, new MoveAction(Direction.RIGHT, false));
            addKeyBinding("up.pressed", KeyEvent.VK_UP, true, new MoveAction(Direction.UP, true));
            addKeyBinding("up.released", KeyEvent.VK_UP, false, new MoveAction(Direction.UP, false));
            addKeyBinding("down.pressed", KeyEvent.VK_DOWN, true, new MoveAction(Direction.DOWN, true));
            addKeyBinding("down.released", KeyEvent.VK_DOWN, false, new MoveAction(Direction.DOWN, false));
        }
    
        @Override
        public void update() {
            repaint();
        }
    
        @Override
        public void setController(GameController controller) {
            this.controller = controller;
            revalidate();
            repaint();
        }
    
        @Override
        public GameController getController() {
            return controller;
        }
    
        @Override
        public Dimension getPreferredSize() {
            Dimension size = new Dimension(13 * 20, 10 * 20);
            GameController controller = getController();
            if (controller != null) {
                int[][] maze = controller.getMaze();
                size.height = maze.length * 20;
                size.width = maze[0].length * 20;
            }
            return size;
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            GameController controller = getController();
            if (controller != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                Dimension size = getPreferredSize();
                int x = (getWidth() - size.width) / 2;
                int y = (getHeight() - size.height) / 2;
    
                int[][] maze = controller.getMaze();
                for (int row = 0; row < maze.length; row++) {
                    int yPos = y + (row * 20);
                    for (int col = 0; col < maze[row].length; col++) {
                        int xPos = x + (col * 20);
                        switch (maze[row][col]) {
                            case 1:
                                g2d.setColor(Color.BLACK);
                                break;
                            default:
                                g2d.setColor(Color.WHITE);
                                break;
                        }
                        g2d.fillRect(xPos, yPos, 20, 20);
                    }
                }
                int[] playerLocation = controller.getPlayerLocation();
                int xPos = x + (playerLocation[0] * 20);
                int yPos = y + (playerLocation[1] * 20);
                g2d.setColor(Color.RED);
                g2d.fillRect(xPos, yPos, 20, 20);
                g2d.dispose();
            }
        }
    
        protected void addKeyBinding(String name, int virtualKey, boolean pressed, MoveAction action) {
            addKeyBinding(name, KeyStroke.getKeyStroke(virtualKey, 0, !pressed), action);
        }
    
        protected void addKeyBinding(String name, KeyStroke keyStroke, MoveAction action) {
            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();
    
            inputMap.put(keyStroke, name);
            actionMap.put(name, action);
        }
    
        public class MoveAction extends AbstractAction {
            private Direction direction;
            private boolean pressed;
    
            public MoveAction(Direction direction, boolean pressed) {
                this.direction = direction;
                this.pressed = pressed;
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                GameController controller = getController();
                if (controller != null) {
                    controller.setDirectionPressed(direction, pressed);
                }
            }
    
        }
    
    }
    

    Okay, this might seem a little weird of such a simple problem, but know imagine you want to add more mazes, that's a simple as changing the model.

    And finally, we need to put it together...

    Maze

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.KeyEvent;
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Set;
    import javax.swing.AbstractAction;
    import javax.swing.ActionMap;
    import javax.swing.InputMap;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.KeyStroke;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class Main {
    
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    GameModel model = new DefaultGameModel();
                    DefaultGameView view = new DefaultGameView();
                    GameController controller = new DefaultGameController(view, model);
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(view);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
    
                    controller.start();
                }
            });
        }
    
        public enum Direction {
            UP, DOWN, LEFT, RIGHT;
        }
    
        public interface GameModel {
            public int[][] getMaze();
            public int[] getPlayerLocation();
            public void setPlayerLocation(int[] location);
            public void update(Set<Direction> directions);
        }
    
        public interface GameController {
            public int[][] getMaze();
            public int[] getPlayerLocation();
            public void setDirectionPressed(Direction direction, boolean pressed);
            public void start();
        }
    
        public interface GameView {
            public void update();
            public void setController(GameController controller);
            public GameController getController();
        }
    
        public class DefaultGameModel implements GameModel {
    
            private int[][] maze
                    = {{1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                    {1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1},
                    {1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1},
                    {1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1},
                    {1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1},
                    {1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1},
                    {1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1},
                    {1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1},
                    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
                    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1}};
    
            private int[] playerLocation = new int[]{1, 0};
    
            @Override
            public int[][] getMaze() {
                return maze;
            }
    
            @Override
            public int[] getPlayerLocation() {
                return playerLocation;
            }
    
            @Override
            public void setPlayerLocation(int[] playerLocation) {
                this.playerLocation = playerLocation;
            }
    
            @Override
            public void update(Set<Direction> directions) {
                int[] location = getPlayerLocation();
                int[][] maze = getMaze();
                int x = location[0];
                int y = location[1];
                if (directions.contains(Direction.UP)) {
                    y--;
                } else if (directions.contains(Direction.DOWN)) {
                    y++;
                }
                if (directions.contains(Direction.LEFT)) {
                    x--;
                } else if (directions.contains(Direction.RIGHT)) {
                    x++;
                }
                if (x < 0) {
                    x = 0;
                } else if (x >= maze[0].length) {
                    x = maze[0].length - 1;
                }
                if (y < 0) {
                    y = 0;
                } else if (y >= maze.length) {
                    y = maze.length - 1;
                }
    
                if (maze[y][x] == 0) {
                    location = new int[]{x, y};
                    setPlayerLocation(location);
                }
    
            }
    
        }
    
        public class DefaultGameController implements GameController {
    
            private GameView view;
            private GameModel model;
    
            private Timer timer;
    
            private Set<Direction> directions;
    
            public DefaultGameController(GameView view, GameModel model) {
                this.view = view;
                this.model = model;
                directions = new HashSet<>(4);
                view.setController(this);
            }
    
            public GameView getView() {
                return view;
            }
    
            public GameModel getModel() {
                return model;
            }
    
            @Override
            public int[][] getMaze() {
                return getModel().getMaze();
            }
    
            @Override
            public int[] getPlayerLocation() {
                return getModel().getPlayerLocation();
            }
    
            @Override
            public void setDirectionPressed(Direction direction, boolean pressed) {
                if (pressed) {
                    directions.add(direction);
                } else {
                    directions.remove(direction);
                }
            }
    
            @Override
            public void start() {
                // This isn't really required for this type of simple example, but what
                // does do is demonstrates at least one possible solution for simple
                // game loop
                //
                // Because of the basic nature of the game, it would be possible to have
                // setDirectionPressed call model.update and view.update
                if (timer != null && timer.isRunning()) {
                    timer.stop();
                }
                timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        getModel().update(Collections.unmodifiableSet(directions));
                        getView().update();
                    }
                });
                timer.start();
            }
    
        }
    
        public class DefaultGameView extends JPanel implements GameView {
    
            private GameController controller;
    
            public DefaultGameView() {
                addKeyBinding("left.pressed", KeyEvent.VK_LEFT, true, new MoveAction(Direction.LEFT, true));
                addKeyBinding("left.released", KeyEvent.VK_LEFT, false, new MoveAction(Direction.LEFT, false));
                addKeyBinding("right.pressed", KeyEvent.VK_RIGHT, true, new MoveAction(Direction.RIGHT, true));
                addKeyBinding("right.released", KeyEvent.VK_RIGHT, false, new MoveAction(Direction.RIGHT, false));
                addKeyBinding("up.pressed", KeyEvent.VK_UP, true, new MoveAction(Direction.UP, true));
                addKeyBinding("up.released", KeyEvent.VK_UP, false, new MoveAction(Direction.UP, false));
                addKeyBinding("down.pressed", KeyEvent.VK_DOWN, true, new MoveAction(Direction.DOWN, true));
                addKeyBinding("down.released", KeyEvent.VK_DOWN, false, new MoveAction(Direction.DOWN, false));
            }
    
            @Override
            public void update() {
                repaint();
            }
    
            @Override
            public void setController(GameController controller) {
                this.controller = controller;
                revalidate();
                repaint();
            }
    
            @Override
            public GameController getController() {
                return controller;
            }
    
            @Override
            public Dimension getPreferredSize() {
                Dimension size = new Dimension(13 * 20, 10 * 20);
                GameController controller = getController();
                if (controller != null) {
                    int[][] maze = controller.getMaze();
                    size.height = maze.length * 20;
                    size.width = maze[0].length * 20;
                }
                return size;
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                GameController controller = getController();
                if (controller != null) {
                    Graphics2D g2d = (Graphics2D) g.create();
                    Dimension size = getPreferredSize();
                    int x = (getWidth() - size.width) / 2;
                    int y = (getHeight() - size.height) / 2;
    
                    int[][] maze = controller.getMaze();
                    for (int row = 0; row < maze.length; row++) {
                        int yPos = y + (row * 20);
                        for (int col = 0; col < maze[row].length; col++) {
                            int xPos = x + (col * 20);
                            switch (maze[row][col]) {
                                case 1:
                                    g2d.setColor(Color.BLACK);
                                    break;
                                default:
                                    g2d.setColor(Color.WHITE);
                                    break;
                            }
                            g2d.fillRect(xPos, yPos, 20, 20);
                        }
                    }
                    int[] playerLocation = controller.getPlayerLocation();
                    int xPos = x + (playerLocation[0] * 20);
                    int yPos = y + (playerLocation[1] * 20);
                    g2d.setColor(Color.RED);
                    g2d.fillRect(xPos, yPos, 20, 20);
                    g2d.dispose();
                }
            }
    
            protected void addKeyBinding(String name, int virtualKey, boolean pressed, MoveAction action) {
                addKeyBinding(name, KeyStroke.getKeyStroke(virtualKey, 0, !pressed), action);
            }
    
            protected void addKeyBinding(String name, KeyStroke keyStroke, MoveAction action) {
                InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
                ActionMap actionMap = getActionMap();
    
                inputMap.put(keyStroke, name);
                actionMap.put(name, action);
            }
    
            public class MoveAction extends AbstractAction {
                private Direction direction;
                private boolean pressed;
    
                public MoveAction(Direction direction, boolean pressed) {
                    this.direction = direction;
                    this.pressed = pressed;
                }
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    GameController controller = getController();
                    if (controller != null) {
                        controller.setDirectionPressed(direction, pressed);
                    }
                }
    
            }
    
        }
    }
    

    This is a basic overview of the general concept and you should consider exploring basic OOP and game theory