Reputation: 633
I'm trying to make a JButton initiate an animation that solves a maze. I have a recursive, depth-first search function that finds the path, but colors it progressively (using Thread.sleep()
). The code works perfectly. The problem I have is with having a button start this animation.
When I add the function into a JButton's actionPerformed
, the solution would only appear after it completely finishes (not step by step). I got to learn that this is because action listeners are single threaded or something like that, so I tried to use Swing Timer. I simply created an event that repaints the JPanel every few milliseconds, thinking that this would keep repainting the JPanel in the background and therefore display the progress in finding the solution. This gave me a runtime error though:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException at Game$1.actionPerformed(Game.java:54) at javax.swing.Timer.fireActionPerformed(Timer.java:313) at javax.swing.Timer$DoPostEvent.run(Timer.java:245) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756) at java.awt.EventQueue.access$500(EventQueue.java:97) at java.awt.EventQueue$3.run(EventQueue.java:709) at java.awt.EventQueue$3.run(EventQueue.java:703) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76) at java.awt.EventQueue.dispatchEvent(EventQueue.java:726) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93) at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
Any ideas on how I could go about initiating the animation from the JButton? I feel this should be way simpler than I'm making it since the animation works just fine on its own and all I want is having it started on the click of a JButton. I thought I'd start with zero code and provide whatever is necessary as requested.
Game.java
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
public class Game {
private static MazeGrid scene;
private static JFrame frame;
private static JButton play;
private static JButton solve;
private static JButton exit;
private static Timer timer;
private static boolean s = false;
public static void init() {
scene = new MazeGrid();
frame = new JFrame();
play = new JButton("Play");
solve = new JButton("Solve");
exit = new JButton("Exit");
frame.setResizable(false);
frame.setSize(900, 700);
frame.setLayout(null);
frame.setLocationRelativeTo(null);
scene.setSize(650, 680);
frame.add(scene, BorderLayout.CENTER);
play.setSize(100, 50);
play.setLocation(650, 10);
frame.add(play);
solve.setSize(100, 50);
solve.setLocation(770, 10);
frame.add(solve);
exit.setSize(100, 50);
exit.setLocation(650, 100);
frame.add(exit);
frame.setVisible(true);
}
public static void main(String[] args) throws InterruptedException {
ActionListener action = new ActionListener(){
@Override
public void actionPerformed(ActionEvent ev) {
if (ev.getSource() == timer) {
scene.repaint();
System.out.println("hello");
}
}
};
timer = new Timer(40, action);
timer.setInitialDelay(0);
timer.start();
init();
play.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
frame.setVisible(false);
init();
}
});
solve.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
scene.solve(1, 1, squares, 40);
timer.stop();
}
});
exit.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
frame.setVisible(false);
frame.dispose();
}
});
}
}
solve
private void solve(int x, int y, Squares squares, int delay) {
// reached middle
if (x == N / 2 && y == N / 2)
done = true;
if (x == 0 || y == 0 || x == N + 1 || y == N + 1)
return;
if (done || m.Visited(x, y))
return;
m.setVisited(x, y, true);
if (!(x == 1 & y == 1)) {
squares.getSquare(x, y).setPath(new Color(180, 140, 50));
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
System.err.println("Error while sleeping!");
Thread.currentThread().interrupt();
}
repaint();
}
if (!m.Top(x, y))
solve(x, y + 1, squares, delay);
if (!m.Right(x, y))
solve(x + 1, y, squares, delay);
if (!m.Bottom(x, y))
solve(x, y - 1, squares, delay);
if (!m.Left(x, y))
solve(x - 1, y, squares, delay);
if (done)
return;
if (!(x == 1 & y == 1)) {
squares.getSquare(x, y).setPath(new Color(230, 230, 230));
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
System.err.println("Error while sleeping!");
Thread.currentThread().interrupt();
}
repaint();
}
}
Upvotes: 0
Views: 171
Reputation: 633
I achieved my desired effect by using a SwingWorker
like so:
class Solver extends SwingWorker<Integer, Integer> {
int delay;
Solver (int delay) {
this.delay = delay;
}
protected Integer doInBackground() throws Exception {
scene.solve(delay);
return 1;
}
}
and added this to the JButton:
@Override
public void actionPerformed(ActionEvent e) {
new Solver(40).execute();
}
Thanks for trying to help.
Upvotes: 0
Reputation: 17524
Call init()
before you start yout Timer
, or the scene
object may be null
when it tries to access it.
init();
ActionListener action = new ActionListener(){
@Override
public void actionPerformed(ActionEvent ev) {
if (ev.getSource() == timer) {
scene.repaint();
System.out.println("hello");
}
}
};
timer = new Timer(40, action);
timer.setInitialDelay(0);
timer.start();
Upvotes: 1