Search code examples
javaswingkeylistenerjapplet

Implementing Keyboard Listener with a Java Applet


I am trying to create a simple game in Java. I am using BlueJ IDE and my code is currently as follows :

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

public class GameGraphic extends JApplet
{

    // Variable initialization
    private Board board;
    private Dice dice;
    private ArrayList<Player> players; 
    private Player currentPlayer;

    // etc..

    public void init()
    {
        setSize(600,800);

        // Code to initialize game, load images
        // etc..

    }

    // Game method etc..

    public void paint(Graphics g)
    {
        // Drawing game board etc..

        turn++;
        int diceRoll = dice.roll();


        advancePlayer(currentPlayer, steps);
        changeCoins(currentPlayer, diceRoll);

        whoseTurn = (whoseTurn+1)%players.size();

        while(command=="w") {
        }

        try {
        Thread.sleep(3000);
        } catch(InterruptedException ex) {
        Thread.currentThread().interrupt();
        } 

        revalidate();
        repaint();
    }
}

So right now, it is used a simulation, everything works well and it goes to the next turn each 3 seconds. What I want to do is use keyboard input to go to the next turn. I want it to basically draw the board, wait until a character is typed, if the character is "n" then advance one turn (basically run paint() for one iteration and wait again). What is the best way to implement this? I tried using KeyListener but it looks like it does not work with AWT. Thank you so much :)


Solution

  • Let's start with, applets are officially a dead technology and I wouldn't waste time trying to make them work, instead, I'd focus your efforts in other areas of the API. See Java Plugin support deprecated and Moving to a Plugin-Free Web for more details.

    You should never call Thread.sleep from within the context of the Event Dispatching Thread (or perform any other long running or blocking operations) and especially not from within the context of a paint method. See Concurrency in Java for more details.

    You should never call any method which could generate a repaint directly or indirectly, painting is for painting and nothing else, doing so could starve the EDT and cause your program to become unresponsive.

    A simple solution to doing animation in Swing is to use a Swing Timer, which won't block the EDT but which triggers it updates within the content of the EDT, making it safe to update the UI from within.

    See How to use Swing Timers for more details.

    I'd also recommend you have a look at Painting in AWT and Swing and Performing Custom Painting because if you intend to do any kind of custom painting, you should have some understand of how the painting process works.

    KeyListener is a low level API, which suffers from keyboard focus issues (if the component it's registered to does not have keyboard focus, it won't generate events), instead, you should be using the Key Bindings API instead.

    In the following example, there are two things happening. The is a Timer which is updating a "running time" value and when you press the N key, it updates a turn variable

    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.FontMetrics;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.KeyEvent;
    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 GameGraphic  {
    
        public static void main(String[] args) {
            new GameGraphic();
        }
    
        public GameGraphic() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new GamePane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class GamePane extends JPanel {
            private int turn = 0;
            private long runtime;
    
            public GamePane() {
                InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
                ActionMap actionMap = getActionMap();
    
                inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0), "next");
                actionMap.put("next", new AbstractAction() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        System.out.println("...");
                        turn++;
                        repaint();
                    }
                });
    
                long startTime = System.currentTimeMillis();
                Timer timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        runtime = System.currentTimeMillis() - startTime;
                        repaint();
                    }
                });
                timer.start();
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g); 
                Graphics2D g2d = (Graphics2D) g.create();
                int width = getWidth();
                int height = getHeight();
                FontMetrics fm = g2d.getFontMetrics();
                String text = Integer.toString(turn);
                int x = (width - fm.stringWidth(text)) / 2;
                int y = ((height - fm.getHeight()) / 2) + fm.getAscent();
                g2d.drawString(text, x, y);
    
                text = Long.toString(runtime);
                x = width - fm.stringWidth(text);
                y = height - fm.getHeight() + fm.getAscent();
                g2d.drawString(text, x, y);
                g2d.dispose();
            }
    
    
        }
    }