JPanel freezes my whole application when trying to draw on it - java

I am coding Oregon Trail for a school project and I am implementing the hunting mini game. We are using model view presenter with a card layout. When the HuntingPanel gets switched to it calls run, and the JOptionPane comes up, but then the whole application freezes and I have to force quit. I coded the entire hunting game in a separate project, and just now brought the files over to the Oregon Trail game. It works fine in its own project with its own JFrame. I'm not sure what to do.
I call this to initialize the panel, switch to it, and run the game.
public void initialize(int ammo) {
player.setBullets(ammo);
bulletLabel.setText("Bullets: "+player.getBullets());
presenter.switchToPanel(OregonTrailPresenter.HUNTING_PANEL);
run();
}
This is my run method.
public void run() {
// starting message
JOptionPane.showMessageDialog(null, "You have reached a nearby field to hunt. You will stay\nhere until " +
"you run out of ammunition or click Return to Trail.");
// while the player has bullets or doesn't click return to trail
while (player.getBullets() > 0 && stillHunting) {
// creates random animals
checkForAnimal();
// moves and updates screen
repaint();
update();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
endHunting();
}
And here are other method used.
private void checkForAnimal() {
int x = 0;
int y = rand.nextInt(MAX_Y)-40;
int rand1 = rand.nextInt(100);
String str = null;
if (rand1 < 50) {
str = "left";
x = MAX_X-40;
}
else if (rand1 >= 50) {
str = "right";
x = 0;
}
double gen = rand.nextGaussian(); // gen is a number from -inf to +inf
gen = Math.abs(gen); // gen is now a number from 0 to inf
if (gen >= 1.9 && gen < 2.1) { //1.19%
animalList.add(new Bunny(x,y,str));
}
if(gen >= 2.1 && gen < 2.2) { //0.9%
animalList.add(new Bear(x,y,str));
}
if (gen >= 2.2 && gen < 2.3) {
animalList.add(new Deer(x,y,str));
}
}
public void update() {
for (int i = 0; i < animalList.size(); i++) {
animalList.get(i).move();
}
}

You have to implement javax.swing.Timer instead of Thread.sleep(int), because this code line freezes all GUI during EDT until Thread.sleep(int) ends. Here is demonstrations what happens if the GUI is delayed during EDT by Thread.sleep(int)

Your program "freezes" because you did not start a new thread for the while loop. Since the panel updates and redraws are handled in the main thread, you are preventing them from happening. To fix this problem you have to start a new thread. You can do this by making your class implement runnable and use new Thread(this).start() to run your loop.
class HuntingGame extends JPanel implements Runnable {
public void initialize(int x) {
//...
new thread(this).start();// This will run your 'run()' method in a new thread.
}
}

Related

How do I dynamically update my Java Swing GUI for a board game from a separate thread?

I am in the process of making a board game with a Java Swing GUI. The game is that of Bao which features an 8x4 playing board of "holes" where each "hole" contains a certain number of seeds. A player can complete two different types of moves these being a capture or non-capture. These moves involve picking up all the seeds from one of the "holes" and placing them one by one in adjacent holes and this is known as a sow.
My game rules are separated into a "Game class" containing the sow function which operates on a 8x4 array of integers.
private int[][] board = new int[4][8];
A portion of the sow function is as follows for placing one seed in an adjacent hole.
BaoGUI gui; // This a reference to the Java Swing GUI passed into the sow function
board[row][column] += 1;
if (gui != null)
{
Thread updateGuiThread = new Thread() {
public void run()
{
try
{
gui.updateBoard();
}
catch (Exception e)
{
System.out.println("Update GUI thread interrupted.");
e.printStackTrace();
}
System.out.println("Finished on " + Thread.currentThread());
}
};
updateGuiThread.start();
Thread.sleep(500);
My Java Swing GUI class holds an object of type "Game" and calls games' sow() function based on user input. As seen above the sow() function places a seed in a hole then moves to the next hole and places another etc.
My updateBoard() function in the GUI class is as follows
public void updateBoard()
{
int[][] board = game.getBoard();
javax.swing.JButton but;
String butName = "";
for (int i = 0 ; i < 4 ; i++)
{
for (int j = 0 ; j < 8 ; j++)
{
butName = "b" + i + j;
but = (javax.swing.JButton)getComponentByName(butName);
but.setText(Integer.toString(board[i][j]));
}
}
}
The effect I want to achieve is that for each placement of a seed the "board" in the GUI should update in an "animated" fashion. However with the above code the updating of the board occurs all at once i.e. only the final board state is displayed without the individual seed placement animation. After some rigorous testing of the above code I managed to ascertain that when I call the Games' sow() function from within the GUI class the sow() function executes on Swings' Event Dispatcher Thread. I thought perhaps that since all the game rule processing was happening on this thread that if I created a new thread for updating the GUI board that each individual seed placement would be seen but to no avail.
I also tried creating a Runnable thread for updating the GUI board as follows.
final Runnable externalUpdateBoard = new Runnable()
{
public void run()
{
int[][] board = game.getBoard();
javax.swing.JButton but;
String butName = "";
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 8; j++) {
butName = "b" + i + j;
but = (javax.swing.JButton) getComponentByName(butName);
but.setText(Integer.toString(board[i][j]));
}
}
try
{
Thread.sleep(1000);
}
catch(Exception e)
{
System.out.println("Exception occured in externalUpdateBoard thread.");
}
}
};
I then ran this executable thread from within my Games' sow() function as follows
Thread updateGuiThread = new Thread()
{
public void run() {
try {
javax.swing.SwingUtilities.invokeAndWait(gui.externalUpdateBoard);
} catch (Exception e) {
System.out.println("Update GUI thread interrupted.");
e.printStackTrace();
}
System.out.println("Finished on " + Thread.currentThread());
}
};
updateGuiThread.start();
Once again the GUI did update but in the same "all at once" fashion without the animations.
How can I get the updateBoard() function im GUI to execute immediately each time a seed is placed in the sow() function?

JFrame skipping some repaint() calls

I'm making a snake game and I've encountered an error.
I have tried two different loops: thread.sleep and Timer.schedule.
I have gotten the same problem.
It will be working fine, but at random intervals, it will start to skip every other frame for 6-10 frames.
In case I wasn't clear, 1 frame is
#Override public void paintComponent(Graphics G){...}
being called. (I have also tried paint)
This has occurred in some other games I've created, but not all. What can I do to fix it?
Here's a full copy of the code:
https://github.com/jnmcd/Snake/blob/master/Code.java
EDIT: I've done some debugging. It appears that the it's not a problem with the paint. The JPanel doesn't always update. What can I do to fix it?
I found what I needed to do. I had to add a revaidate() after the repaint().
Also In the checkKillCollisions you have to break the loop right after you found the losing condition.
Also if the game ends it keeps on showing the error message[Dialog] for which there i no end.So I have created a flag gameOver to check is game is over or not in Snake Class
static Boolean gameOver = false;//Defined in Snake Class
public void checkKillCollisions() {
boolean lose = false;
for (int i = 1; i < Snake.segments.size(); i++) {
if (Snake.segments.get(i).x == x && Snake.segments.get(i).y == y) {
lose = true;
break;//Have to do this
}
}
if (x >= 60 || x < 0 || y >= 60 || y < 0) {
lose = true;
}
if (lose) {
Snake.window.popUp("You Lose");
}
Snake.gameOver = lose;//Will set the gameOVer flag in Snake class
}
And I've modified the Loop class to stop running right after the gameOver flag is set to true
class Loop extends TimerTask {
#Override
public void run() {
if (!Snake.gameOver) {
Snake.updates();
Snake.window.render();
} else {
System.out.println("Game Over");
cancel();
Snake.window.dispose();
}
}
}

Java Swing Sprite: how to simulate a rolling dice

In a game board we are developing in Java we want to show a sort of rolling dice when a button is pressed. We are trying to use the following image as a sprite:
so when the button is pressed, here's what happens:
private void startAnimationDie(final JPanel panel) {
int launchResult = /* getting a launch dice result from the core game (from 1 to 6)*/
new Thread() {
public void run() {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
BufferedImage[] animationBuffer = initAnimationBuffer();
BufferedImage[][] exactDieFaces = initExactDieFaces();
int launchResult = coreGame.launchDie();
coreGame.getMyPartecipant().setLastLaunch(launchResult);
AnimationSprite animation = new AnimationSprite(animationBuffer, Constants.DIE_ANIMATION_SPEED);
animation.start();
JLabel resultDie = new JLabel();
resultDie.setBounds(60, 265, Constants.DIE_SIZE, Constants.DIE_SIZE);
for (int counter = 0; counter < Constants.DIE_ANIMATION_SPEED * 100; counter++) {
animation.update();
//panel.removeAll();
panel.updateUI();
//System.out.println("infor");
resultDie.setIcon(new ImageIcon(animationBuffer[counter % Constants.ROTATIONS]));
panel.add(resultDie);
panel.updateUI();
updateUI();
}
panel.removeAll();
panel.updateUI();
AnimationSprite resultAnimation = new AnimationSprite(exactDieFaces[launchResult - 1], 6);
resultAnimation.start();
resultAnimation.update();
System.out.println("0");
resultDie.setIcon(new ImageIcon(exactDieFaces[launchResult - 1][0]));
System.out.println("1");
resultDie.setBounds(60, 265, Constants.DIE_SIZE, Constants.DIE_SIZE);
System.out.println("2");
panel.add(resultDie);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
userPlayer.getGamePanel().makePossibleMoveFlash();
}
});
} catch (Exception ex) {
}
}
}.start();
where exactDieFaces contains the dice faces according to the pawn color which is launching and that face will be shown after the simulated rolling has finished; on the other hand, animationBuffer contains 40 or 50 random faces of all colors taken from the sprite image like that:
private BufferedImage[] initAnimationBuffer() {
BufferedImage[] result = new BufferedImage[Constants.ROTATIONS];
int rowSprite, colSprite;
Random random = new Random();
for (int i = 0; i < Constants.ROTATIONS; i++) {
rowSprite = 1 + random.nextInt(5);
colSprite = 1 + random.nextInt(5);
result[i] = DieSprite.getSpriteExact(0 /* offset */,colSprite, rowSprite);
}
return result;
}
The problem is that nothing is shown during the animation: I expected to see many dice faces change one after the other until the for cycle ends but nothing is shown...The only thing I see is the launch result according to the color pawn which has launched the die but meanwhile there is nothing...can you help me?
If I understand your code correctly you are attempting to update the UI many times within the same for loop. That's not how animation works. You need a timer which regularly notifies you to move to the next frame to view.
A good place to start in understanding how to use timers is here in the Java tutorial.

Java Swing Timers & Threads &only last command is being executed in For Loop

Hope you are all well.
Also I'm so sorry for how long winded this is. And I'm new to Java so please forgive me for my lack of knowledge/terminology/Java conventions.
Basically I've created a program which takes user input and moves the vehicle across the surface area. So the user input can be "50 left 4" so that means go 50 meters forward, turn left and go 4 meters forward.
For the vehicle I'm using a paint method on a JPanel. When the vehicle moves forward, it initially jumped from one side of the area to another. I wanted to be able to see it moving meter by meter. So I added a Swing Timer which moves the vehicle 1 meter, pauses for a second, moves the vehicle 2 meter and so on.
Now, the problem is that when I enter the commands "50 left 4", the vehicle simply turns left and then moves 4 meters forward. It ignores the first number command. The same happens when I enter "3 4", it will ignore 3 and just move 4 meters forward. Also when I enter "3 left", it will turn left first and then move 3 meters forward. Now I've got methods which takes the user input, chop it up into an array, feed each element of the array through a loop, check if it's an integer or not. If it is an integer it moves the vehicle forward, if not it turns the vehicle either left or right. That all works fine and I'm happy with it.
So what I thought I'd do is have the class which moves the vehicle implement Runnable so that this method will be executed from a separate thread, making the main thread wait and that way it will won't ignore every command except for the last. But that doesn't work either.
Here is the movement class which moves the vehicle forward 1 meter at a time using a Swing Timer.
It implements Runnable. It's abstract because I needed the run method to take the user input commands as a parameter. Is that what's causing the problem?
public abstract class Movement implements Runnable {
static int count = 0;
static int distance;
public static void moveRover() {
// Every 1 second the timer will move the Rover 40 pixels.
at.translate(0, 40);
}
public static void getDistance(int num) {
distance = num;
}
static Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
moveRover();
count++;
System.out.println(count);
if (count == distance) {
stop();
System.out.println("Timer stopped");
count = 0;
}
}
});
public static void start(int num) {
getDistance(num);
System.out.println("Distance rover will travel: " + distance);
System.out.println("\nTimer started");
timer.start();
}
public static void stop() {
timer.stop();
}
public void run(int num) {
start(num);
}
public Movement(int num) {
System.out.println("Start RUN method");
run(num);
}
And here is the code from another class which runs the for loop and thread:
public static void action(final String[] commands) {
for (int i = 0; i < commands.length; i++) {
if (isInteger(commands[i])) {
int distance = Integer.parseInt(commands[i]);
Movement h1 = new Movement(distance) {
#Override
public void run() {
System.out.println("Empty run method");
}
};
Thread t1 = new Thread(h1);
t1.start();
System.out.println(t1.isAlive());
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
Direction.start(commands[i]);
String pos = Direction.getDirection(); // GET DIRECTION
}
}
}
I really appreciate you reading all that, thank you so much!
And thank you to anyone who replies! If you know where I'm going wrong and what I can do to fix it, you're an absolute lifesaver, I really appreciate it :D
So yeah, you need to learn about Threading in Java. You should NEVER use Thread.start() as of Java 1.6 and later. Quite frankly, I'm not even sure why you're using Threads in your for-loop. Ignoring for the moment all of your questionable design choicess, try this code:
public static void action(final String[] commands) {
for (int i = 0; i < commands.length; i++) {
if (isInteger(commands[i])) {
int distance = Integer.parseInt(commands[i]);
new Movement(distance) {
#Override
public void run() {
System.out.println("Empty run method");
}
}.run();
} else {
Direction.start(commands[i]);
String pos = Direction.getDirection(); // GET DIRECTION
}
}
}

Why does my Loop mechanism not work?

Sorry if this is a very closed question that won't be useful to others, but I'm just stumped by this bug and I haven't been able to solve it for weeks!
I am working on a wave based survival game and am currently working on a spawning mechanism.
The code I wrote works perfectly for one wave, but somehow doesn't restart for further waves.
I have written the code below:
public void run() {
while (ingame) {
if (enemyList.isEmpty()) {
stopSpawn = false;
try {
Thread.sleep(500);
spawnNewEnemy();
} catch (InterruptedException ex) {
System.out.println("Interrupted");
}
} else {
if (!enemyList.isEmpty() && !stopSpawn) {
// getEnemyAmount returns the amount of enemies that should be spawned this wave
for (int enemiesSpawned = 0; enemiesSpawned < getEnemyAmount(); enemiesSpawned++) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
}
System.out.println(currentWave);
spawnNewEnemy();
}
stopSpawn = true;
}
}
}
}
Here is the spawnNewEnemy method
public void spawnNewEnemy() {
Random spawn = new Random();
int spawnX = spawn.nextInt(500);
int spawnXTest = b.bX - spawnX;
if (spawnXTest < 20) {
spawnX = spawnX + 20;
} else {
}
int spawnY = spawn.nextInt(500);
int spawnYTest = b.bX - spawnY;
if (spawnYTest < 20) {
spawnY = spawnY + 20;
} else {
}
spawnY = spawnY + 20;
spawnX = spawnX + 20;
enemyList.add(new Enemy(spawnX, spawnY));
}
I can read the following in your code:
If the list of enemies is empty, you set stopSpawn to false and spawn an enemy.
That triggers your else-statement.
There, you spawn enemies based on the enemy count.
stopSpawn is set to true, thus your else-statement doesn't get triggered anymore.
Nothing happens anymore untill your enemylist is empty.
If your enemylist is empty again, you start over.
The logics seems ok, so I'm thinking either the way you spawn enemies through spawnNewEnemy() is faulty, or the way you remove enemies from the enemyList is faulty. I see neither of that code so that is as far as I can go in this answer.
I guess your problem with a loop is in stopSpawn value.
You set it to true after the first wave and likely not setting to `false' before starting the next wave.

Categories