Why is my Java game crashing when I add a game loop? - java

I've recently started to try to learn java. I thought maybe making small games would be the best way to get familiar with java, so that's what I'm doing. So, I started my first game, and it's looking good. It's just a grid with a player in the center. Once I tried adding player movement I realized I'd need a game loop to check for key presses. That's what I tried. All I can say is that I've never made a game loop before, so this is what I attempted:
public static void main(String[] args) {
GamePanel panel = new GamePanel();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createGui();
double ns = 1000000000.0 / 60.0;
double delta = 0;
long lastTime = System.nanoTime();
long timer = System.currentTimeMillis();
while (true) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
panel.checkKeys();
delta--;
}
}
}
});
}
Here's the issue... When I run this, my game just crashes. It doesn't even let me close out of the game. What is going on? Just in case, I will show more of the GamePanel code (please tell me if you need any more code).
public GamePanel() {
setLayout(new GridLayout(tiles_count, tiles_count));
setBackground(Color.gray);
setFocusable(true);
addKeyListener(this);
tile_size = 40;
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_A) {
System.out.println("A PRESSED");
player.x += 40;
left = true;
}
}
#Override
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_A) {
System.out.println("A RELEASED");
left = false;
}
}
#Override
public void keyTyped(KeyEvent e) {
}
public void checkKeys() {
if(left) {
System.out.println("LEFT");
} else {
System.out.println("!LEFT");
}
}
The game structure is like this: I have a Main class, which runs a GamePanel class as the main(String args) {} function. This issue might have to be with the fact that im using the run method in the Main class instead of the GamePanel class.
What am I doing wrong? Any help is very much appreciated!

Your while loop will never exit, and thus your program can never repaint, etc.
However, for a game loop like this, you do want an infinite loop --- you just want to run it in a different thread. In this way, the rest of your program is not blocked by the infinite loop, however you are still able to run your game loop tasks.
To do this, we make a new thread, and run our loop in there instead. Note also that after each loop we sleep for a little while (ideally you'd want to do this with time deltas, etc, this is just an example) --- this means that we cap the amount of frames per second, meaning that we don't overwork the processor for no good reason.
Thread t = new Thread(new Runnable(){
public void run() {
while (true) {
// your update logic here
try {
Thread.sleep(16);
}
catch(InterruptedException) {}
}
}
});
t.start();

I believe the issue is that you have an infinite loop. This occurs when the condition for your loop will always remain true. while(True). You either need to implement a break; statement which will terminate the loop or use a condition in your while loop that will run the loop until a certain condition is met while(some-condition == true). Hopefully, this helps.
https://codesnippets.fandom.com/wiki/Infinite_Loop

Related

Java Swing and Thread sleeping

I decided to make something fun as a break from reading programming books, and I have hit a snag. This is my first swing program, and am ready to wrap it up!
The problem: I clearly don't understand how threads work with Swing. I am writing a GUI for a black jack game, and I did all of the functionality first e.g. drawing a new card to the screen when the player hits, showing the dealer hit after the player decides to stay, etc. This all works.
When I added in the logic to check for a user bust when hitting, or who wins when the user decides to stay, the game instantly goes to the win/loss screen before drawing either: the card the user got that caused a bust, or; the cards that the dealer drew when hitting (if any).
I tried inserting Thread.sleep in various places, to no avail. The program would sleep before drawing the card, then end instantly as above (even though it was placed logically after the call to draw, and before the call to calculate a winner).
also I tried to follow the MVC paradigm here, just fyi.
P.S. my program runs on one thread, I do not explicitly instantiate another, but I vaguely remember reading that Swing spawns it's own thread for graphics stuff
Sorry for that long intro! Here is some code:
The Model class' pertinent methods
void hit() {
//run when button is clicked
player.hand.add(deck.deck.get(0));
deck.deck.remove(0);
}
boolean isBust() {
if (player.getScore() > 21)
return true;
return false;
}
void dealerHit() {
while (dealer.getScore() < 17) { //could implement soft 17 rules for more difficulty
dealer.hand.add(deck.deck.get(0));
setChanged();
notifyObservers();
deck.deck.remove(0);
//Here was one attempt
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
boolean isWin() {
//and another
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if ((player.getScore() > dealer.getScore() && player.getScore() <= 21) || dealer.getScore() > 21)
return true;
return false;
}
void stay() {
dealerHit();
isWin();
}
View Class
void addHitListener(ActionListener HitListener) {
hit.addActionListener(HitListener);
}
void addStartListener(ActionListener StartListener) {
start.addActionListener(StartListener);
}
void addStayListener(ActionListener StayListener) {
stay.addActionListener(StayListener);
}
void display() {
JFrame myFrame = new JFrame("BlackJack");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setContentPane(this);
myFrame.setPreferredSize(new Dimension(700,550));
myFrame.pack();
myFrame.setVisible(true);
}
void addCards(Player p, Dealer d) {
topPanel.remove(start);
pcardPanel.add(playerlabel);
dcardPanel.add(dealerlabel);
for (Card c : p.hand) {
ImageIcon cc = new ImageIcon(c.img);
JLabel cC = new JLabel(cc);
//cC.setAlignmentX(alignmentX); use to get X alignment of card 1 & 2 for splits
//cC.setAlignmentY(alignmentY); same for Y, then increment by .3f
pcardPanel.add(cC);
}
for (Card c : d.hand)
dcardPanel.add(new JLabel(new ImageIcon(c.img)));
topPanel.add(new JLabel("Options: "));
topPanel.add(hit);
topPanel.add(stay);
validate();
repaint();
}
void endGame(boolean isWin) {
//I think I tried here, too
removeAll();
setBackground(new Color(0, 122, 0));
if (isWin == true)
add(new JLabel("You won!"));
else
add(new JLabel("You Lost"));
validate();
repaint();
}
public void hitPlayer(Player p) {
JLabel hits = new JLabel(new ImageIcon(p.hand.get(p.hand.size()-1).img));
//hits.setAlignmentY(alignmentY);
pcardPanel.add(hits);
validate();
repaint();
}
public void hitDealer(Dealer d) {
dcardPanel.add(new JLabel(new ImageIcon(d.hand.get(d.hand.size()-1).img)));
validate();
repaint();
}
Controller class:
public class Controller implements Observer {
BlackJack game;
Table t;
Controller(BlackJack game, Table t) {
this.game = game;
this.t = t;
this.game.addObserver(this);
this.t.addHitListener(new HitListener());
this.t.addStartListener(new StartListener());
this.t.addStayListener(new StayListener());
}
public void go() {
t.display();
}
public void update(Observable obj, Object observed) {
t.hitDealer(game.getDealer());
}
class HitListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
game.hit();
t.hitPlayer(game.getPlayer());
if (game.isBust() == true)
t.endGame(false);
}
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.addCards(game.getPlayer(), game.getDealer());
}
}
class StayListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
game.stay();
//doStay();
if (game.isWin() == true)
t.endGame(true);
else
t.endGame(false);
}
}
I just had a thought, since I'm doing this in the actionPerformed methods, could that be why sleep seemed to affect the GUI thread, and not draw the card(s) then sleep. I bet that is it. But I'm going to eat dinner, hopefully someone smarter than myself can lend a hand! Thanks in advance
P.P.S. if there are any typos (I don't think there are) just know that it all compiles and works! And no warnings, if that helps
Swing is indeed a single-thread library, like most UIs. There are also many optimizations to make it work fast. Case in point - most paintings are cached and displayed together. Even if this was not the case, you'd be relying on the speed of the system, which is not a good idea.
If you want a delayed action, you need to use swing's timer (not to be confused with the other timer class). That class has an action listener that goes off when the timer expires. In your case, you'd detect the win/bust condition, start the timer (e.g to fire in 2 seconds) and continue the drawing as usual.
This is a little too long for me to read and understand... But I guess your problem is because you are processing everything in the Event Dispatching Thread.
When you paint something on the GUI and then have more processing, the paint will actually reflect on the GUI only when the whole of the thread has finished processing. That's why you are not seeing your drawing before your Thread.sleep method.
Instead you should use SwingWorker to split your processing and GUI updating to different threads.
Look at this https://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html

Print array every 1 second

I want to make a game that loops through an array every 1 second and then when the 'x' key is pressed the array stops printing, and the next 'x' key press the array resumes from the current element and loops again. my game starts with x and if i press again it just prints every 1 second. how do i add functionality and keep track of cards in the deck
My program is in java frame
public class Game {
private JFrame frame;
public static String[] cards = { "Jack", "Queen", "King" };
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Menu window = new Menu();
Timer timer = new Timer();
window.frame.setVisible(true);
//timer for card loops
timer.schedule(new GoCards(cards), 0, 1000);
window.frame.addKeyListener(new KeyListener() {
#Override
public void keyPressed(KeyEvent e)
{
char key = e.getKeyChar();
if(key == 'x')
{
//pause the timer and if i press again then resume timer
timer.cancel();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
class GoCards extends TimerTask
{
String[] deck;
public GoCards(String[] cards)
{
deck = cards;
}
public void run()
{
for(int i = 0; i < 3; i++)
{
Thread.sleep(1000);
System.out.println(deck[i]);
}
}
Yours is a Swing application, so no, don't use Thread.sleep(...), not unless you want to risk putting your entire GUI to sleep, and don't use a java.util.Timer, since this does not work well with the Swing event thread. Instead you will want to use the tool best suited for the job -- a Swing Timer, aka javax.swing.Timer. The Timer can be started by calling start() on it and stopped simply by calling stop(), and you set the delay in milliseconds in its constructor. Also, use of a KeyListener is risky as it won't work if anything, such as a JButton for instance, steals the focus. Better to use Key Bindings. You can find links to the Swing tutorials and other Swing resources here: Swing Info.
You can make use of a timer such as wait
Thread.sleep(milliseconds);
Then keep track of each key press / iteration with a new counter n.
Checking for key presses would be a matter of simply handling them as they come in, or something as arbitrary as checking each iteration for any particular key.

Java: Perform Action Every X Seconds

I've got a working Java program and I would like to draw an object on the display every X seconds. What is the best way to do this? I was thinking of using a for loop and some sleep statements, but I'm curious if there is an easier or more efficient way to go about this.
Thanks.
The simplest way would be to use a javax.swing.Timer
Timer timer = new Timer(X, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// Update the variables you need...
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
You might also like to have a read through
The Event Dispatching Thread
Concurrency in Swing
So you can understand why you should never use a while (true) { Thread.sleep(X) } call in Swing (inside the EDT)
ScheduledExecutorService might help here. The Javadoc shows example usage. Don't forget to call the shutdown method when you're finished.
Using Thread, this will draw a rectangle on the screen every XMilSeconds. This will stop after 5 runs. Edit the xMilSeconds for slower runs, and j > 4 for how many runs before stoping. It does freeze though, that I can't fix.
int i = 0;
private long xMilSeconds = 300;
private boolean paint;
public boolean running = true;
public void paint(Graphics g)
{
super.paint(g);
if(paint)
{
for(;i < i+1;)
{
g.drawRect(i+49,i+49,i+299,i+99);
g.setColor(Color.RED);
g.fillRect(i+49,i+49,i+299,i+99);
}
paint = false;
}
}
public void run()
{
while(running)
{
try
{
Thread.sleep(xSeconds);
paint = true;
repaint();
i++;
j++;
if(j > 4)
{
running = false;
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}

Multiple swing timers

I have (what should be) a simple problem to tackle and I'm open to other ways to solve it. I am open to other solutions.
The problem:
We are using java swing to display the graphics of a turn-based, tile-based game. I'm using jlabels with icons, absolutely positioned.
To animate the movement, I am using a swing timer that updates the location by 4 pixels at a time, slowly moving the sprite right, left, etc.
To achieve this initially, I was running a timer, which works wonderfully. The problem comes in when I try to move down, then move right.
The sprite moves down, never moves right, and if I watch the execution with some console printing, it's clear to see that both timers are running at the same time. I've done a fair amount of digging on the internet and I wasn't able to find a way to tell a swing timer not to execute until the first timer has stopped, and if I try to busy-wait until one timer finishes (yuck) the UI never displays at all (clearly a step in the wrong direction.)
Now I can convert away from timers altogether and either have the sprite teleport to its new location, or use some awful busy-wait movement scheme, but I'm hoping some kind soul has a solution.
In short: I need a way to run a swing timer for a set period of time, stop it, and then start a new timer, so that they do not overlap. Preferably this method would allow each timer to be in its own method, and I could then call the methods one after the other.
Thanks in advance for any advice you might have.
Edit: Expanded example code. If a full scsse is a requirement for your advice then I'm sorry to have wasted your time, because the full code is a beast. This sample code does not work at all as it stands, sorry, but it should illustrate the point.
So. We have two functions, each with a timer that runs an animation cycle, one for moving down and right diagonally, one for moving straight down.
public class TestClass {
static int counter = 0;
static int counter2 = 0;
static Timer timerC;
static Timer timerX;
public static void main(String[] args) {
moveC();
moveX();
}
public static void moveC() {
int delay = 200; // milliseconds
timerC = new Timer(delay, null);
timerC.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (counter < 32) {
counter = counter + 4;
System.out.println("*C*");
} else {
timerC.stop();
System.out.println("*C STOP*");
}
}
});
timerC.start();
}
public static void moveX() {
int delay = 200; // milliseconds
timerX = new Timer(delay, null);
timerX.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (counter < 32) {
counter = counter + 4;
System.out.println("*X*");
} else {
timerX.stop();
System.out.println("*X STOP*");
}
}
});
timerX.start();
}
}
What I would want to see here eventually would be
*C*
*C*
*C*
*C*
*C STOP*
*X*
*X*
*X*
*X*
*X STOP*
What I actually get is
*C*
*X*
*C*
*X*
*C*
*X*
*C*
*X*
*C STOP*
*X STOP*
The point I'm trying to get at here is running one animation cycle to completion, then the other.
Thanks again.
Don't use multiple Timers, but rather only one Timer that deals with each direction as it's needed. You need some type of queue to hold the direction information, either a formal queue or a collection that you use as a queue (first in, first out), and then have your Timer extract the direction from this queue as it's running. For example, here I use my JList's model as my queue by removing and using the Direction that was added first (at the top of the JList):
import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.*;
public class TimerPlay extends JPanel {
private DefaultListModel directionJListModel = new DefaultListModel();
private JList directionJList = new JList(directionJListModel);
JButton startTimerButton = new JButton(
new StartTimerBtnAction("Start Timer"));
public TimerPlay() {
ActionListener directionBtnListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent actEvt) {
String actionCommand = actEvt.getActionCommand();
Direction dir = Direction.valueOf(actionCommand);
if (dir != null) {
directionJListModel.addElement(dir);
}
}
};
JPanel directionBtnPanel = new JPanel(new GridLayout(0, 1, 0, 10));
for (Direction dir : Direction.values()) {
JButton dirBtn = new JButton(dir.toString());
dirBtn.addActionListener(directionBtnListener);
directionBtnPanel.add(dirBtn);
}
add(directionBtnPanel);
add(new JScrollPane(directionJList));
add(startTimerButton);
}
private class StartTimerBtnAction extends AbstractAction {
protected static final int MAX_COUNT = 20;
public StartTimerBtnAction(String title) {
super(title);
}
#Override
public void actionPerformed(ActionEvent arg0) {
startTimerButton.setEnabled(false);
int delay = 100;
new Timer(delay, new ActionListener() {
private int count = 0;
private Direction dir = null;
#Override
public void actionPerformed(ActionEvent e) {
if (count == MAX_COUNT) {
count = 0; // restart
return;
} else if (count == 0) {
if (directionJListModel.size() == 0) {
((Timer)e.getSource()).stop();
startTimerButton.setEnabled(true);
return;
}
// extract from "queue"
dir = (Direction) directionJListModel.remove(0);
}
System.out.println(dir); // do movement here
count++;
}
}).start();
}
}
private static void createAndShowGui() {
TimerPlay mainPanel = new TimerPlay();
JFrame frame = new JFrame("TimerPlay");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum Direction {
UP, DOWN, LEFT, RIGHT;
}
For reference, this example manages four instances of Timer, two of which run (interleaved) while hovering in any corner. You might compare it to your approach. This related answer discusses animation in a similar tile-based game.
put all Icons in some form of array
create a single Swing Timer with a short delay
in Swing ActionListener, take each `Icon from the array, getBounds from screen, move Icon one step
repeat until target reached.

Trigger event only after repaint in Java Swing?

I am making a simple board game in java, where I want to animate a dice roll. So I flash pictures of a dice like this:
public Timer roll_dice = new Timer(50, this);
...
public void actionPerformed(ActionEvent evt) {
if(roll_dice.getDelay() > 500){
roll_dice.setDelay(50);
roll_dice.stop();
movePiece();
}else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
}
movePiece(){
//do some more painting
}
So the die is going so show random numbers for a few times, and then slowly settle on a number. After that is done I would like to call the movePiece() method. However, as it is, the the repaint occurs sporadically and screws everything up so that movePiece() gets called before the dice roll is actually finished animating.
Does anyone have any ideas how I can call movePiece only after the final repaint has happened?
So the die is going so show random numbers for a few times, and then slowly settle on a number. After that is done I would like to call the movePiece() method. However, as it is, the the repaint occurs sporadically and screws everything up so that movePiece() gets called before the dice roll is actually finished animating.
What worries me here is why your painting is occurring sporadically -- it simply shouldn't be doing that, and perhaps that is what you need to fix. I wonder if you're reading in the images from the file each time you do the drawing or some other cause for slowing the drawing down. If you need more help regarding this issue, then you'll have to give us more information on how you do your painting. Regardless, you should avoid having program logic be dependent on painting as you don't have full control over when or even if painting will occur.
Rather than redrawing images and calling repaint(), why not simply put your rolling dice images into ImageIcons on program start up, and then in your Swing Timer, swap icons in a JLabel? Then stop your Timer when the delay gets long enough and in that if block, move your piece.
So, assuming that you have several dice, each can be displayed by a JLabel that is held in an array of JLabel called diceLabels, and the ImageIcons can be held in an array called diceIcons. Then you can do something like:
public void actionPerformed(ActionEvent e) {
if (roll_dice.getDelay() > 500) {
roll_dice.setDelay(50);
roll_dice.stop();
movePiece(); // I like this -- this shouldn't change
} else {
roll_dice.setDelay(roll_dice.getDelay() + 50);
// dice_panel.repaint(0);
for (JLabel dieLabel : diceLabels) {
int randomIndex = random.nextInt(diceIcons.length);
dieLabel.setIcon(diceIcons[randomIndex]);
}
}
}
I like your logic on when you call movePiece() and I think that this should remain unchanged.
You can call the rolling in another thread and join() the current thread to the rolling one. That way the main code will wait until the roll thread dies (finished rolling).
public void actionPerformed(ActionEvent evt) {
if(roll_dice.getDelay() > 500){
Thread rollerThread = new RollerThread();
rollerThread.start();
rollerThread.join();
movePiece();
}
else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
private RollerThread extends Thread
{
public void run(){
roll_dice.setDelay(50);
roll_dice.stop();
}
}
However, this might not work with the EDT - because repaints should be scheduled to the queue. Maybe you can shedule the event using the SwingUtilities.invokeAndWait():
public void actionPerformed(ActionEvent evt) {
Thread thread = new Thread(){
public void run(){
if(roll_dice.getDelay() > 500){
SwingUtilities.invokeAndWait(new Runnable(){
public void run(){
roll_dice.setDelay(50);
roll_dice.stop();
}
});
movePiece();
}
else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
};
thread.start();
}
Does anything change if you put that call to movePiece(); in a SwingUtilities.invokeLater(Runnable);?
if(roll_dice.getDelay() > 500){
roll_dice.setDelay(50);
roll_dice.stop();
SwingUtilities.invokeLater(new Runnable() {
public void run() { movePiece(); }
});
}
...

Categories