1

Right now, I am currently making a marbles game where two players try and shoot their marbles into a circle. The player with more marbles in the circle wins.

The player whose turn it is will click and drag in the opposite direction of which they want the ball to move (sort of like a sling shot). In my Marble class, there is a thread which starts when the mouse click is released. The marble will move and bounce off the walls, slowly reaching a X and Y velocity of zero.

When this happens though, my thread continues looping and eating through the computers CPU.

You can tell that the thread is still running because it continues printing out the velocity even when the ball has stopped moving.

I need help trying to figure out a efficient way of stopping the thread without using the Thread class's stop() method.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Ball
{
    float x, y, lastX, lastY;
    int width, height;
    float xVelocity, yVelocity;
    float speed;
    public Ball()
    {        
        width = (int) (Math.random() * 50 + 10);
        height = (int) (Math.random() * 50 + 10);
        x = (float) (Math.random() * (Marble.gamePanel.getWidth() - width) + width/2);
        y = (float) (Math.random() * (Marble.gamePanel.getHeight() - height) + height/2);
        lastX = x;
        lastY = y;
        xVelocity = (float) Math.random() * speed*2 - speed;
        yVelocity = (float) Math.random() * speed*2 - speed;
    }

    public void update()
    {
        lastX = x;
        lastY = y;
        x += xVelocity;
        y += yVelocity;

        if(x + width/2 >= Marble.gamePanel.getWidth())
        {
            xVelocity *= -0.75;
            x = Marble.gamePanel.getWidth() - width/2;
        }
        else if (x - width/2 <= 0)
        {
            xVelocity *= -0.75;
            x = width/2;
        }

        if (y + height/2 >= Marble.gamePanel.getHeight())
        {
            yVelocity *= -0.75;
            y = Marble.gamePanel.getHeight() - height/2;
        }
        else if (y - height/2 <= 0)
        {
            yVelocity *= -0.75;
            y = height/2;
        }
    }
}

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;
/**
 * Write a description of class Marble here.
 * 
 * @author (Ryan Lawes) 
 * @version (5-23-14)
 */
public class Marble extends JFrame implements MouseListener, MouseMotionListener
{
    public static GamePanel gamePanel = new GamePanel();
    private boolean running = false;
    private int fps = 60;
    private int frameCount = 0;
    private int mouseXC;//X coordinate of where the mouse was clicked
    private int mouseYC;//Y coordinate of where the mouse was clicked
    private int mouseXR;//X coordinatte of where the mouse was released
    private int mouseYR;//Y coordinatte of where the mouse was released
    public Marble()
    {
        super("Fixed Timestep Game Loop Test");
        Container cp = getContentPane();
        cp.setLayout(new BorderLayout());
        JPanel p = new JPanel();
        p.setLayout(new GridLayout(1,2));
        cp.add(gamePanel, BorderLayout.CENTER);
        cp.add(p, BorderLayout.SOUTH);
        setSize(500, 500);
        addMouseListener(this);
        addMouseMotionListener(this);
    }      

    public static void main(String[]args)
    {   
        Marble mrb = new Marble();
        mrb.setVisible(true);
    }      

    //Starts a new thread and runs the game loop in it.   
    public void runGameLoop()   
    {
        Thread loop = new Thread()
            {
                public void run()
                {
                    gameLoop();
                }
            };
        loop.start();
    } 

    //Only run this in another Thread!
    private void gameLoop()
    {      
        //This value would probably be stored elsewhere.      
        final double GAME_HERTZ = 30.0;
        //Calculate how many ns each frame should take for our target game hertz.
        final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
        //At the very most we will update the game this many times before a new render.
        //If you're worried about visual hitches more than perfect timing, set this to 1.
        final int MAX_UPDATES_BEFORE_RENDER = 5;
        //We will need the last update time.
        double lastUpdateTime = System.nanoTime();
        //Store the last time we rendered.
        double lastRenderTime = System.nanoTime();
        //If we are able to get as high as this FPS, don't render again.
        final double TARGET_FPS = 60;
        final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
        //Simple way of finding FPS.
        int lastSecondTime = (int) (lastUpdateTime / 1000000000);
        while(running)
        {
            double now = System.nanoTime();
            int updateCount = 0;

            //Do as many game updates as we need to, potentially playing catchup.
            while(now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER)
            {   
                gamePanel.update();
                lastUpdateTime += TIME_BETWEEN_UPDATES;
                updateCount++;
            }
            //If for some reason an update takes forever, we don't want to do an insane number of catchups.
            //If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.

            if(now - lastUpdateTime > TIME_BETWEEN_UPDATES)
            {
                lastUpdateTime = now - TIME_BETWEEN_UPDATES;
            }

            //Render. To do so, we need to calculate interpolation for a smooth render.
            float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES));
            drawGame(interpolation);
            lastRenderTime = now;
            //Update the frames we got.
            int thisSecond = (int) (lastUpdateTime / 1000000000);
        }

        //////if(gamePanel.ballXVel == 0 && gamePanel.ballYVel == 0)
        //////{
        //////    running = !running;
        //////}
    }

    private void drawGame(float interpolation)
    {
        gamePanel.setInterpolation(interpolation);
        gamePanel.repaint();
    }

    public void mouseClicked(MouseEvent e)
    {

    }

    public void mousePressed(MouseEvent e)
    {
        /**
         * I want to make it so you have to wait until the marble is done rolling before you can click again
         */
        mouseXC = (int) e.getPoint().getX();
        mouseYC = (int) e.getPoint().getY();
    }

    public void mouseReleased(MouseEvent e)
    {
        mouseXR = (int) e.getPoint().getX();
        mouseYR = (int) e.getPoint().getY();
        gamePanel.ballXVel = (mouseXC - mouseXR)/2;
        gamePanel.ballYVel = (mouseYC - mouseYR)/2;
        //////////while(gamePanel.ballXVel != 0 && gamePanel.ballYVel != 0)
        //////////{
        running = !running;
        runGameLoop();
        ////////}
    }

    public void mouseEntered(MouseEvent e)
    {

    }

    public void mouseExited(MouseEvent e)
    {

    }

    public void mouseDragged(MouseEvent e)
    {

    }

    public void mouseMoved(MouseEvent e)
    {

    }
}

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class GamePanel extends JPanel
{
    float interpolation;
    float ballX, ballY, lastBallX, lastBallY;
    int ballWidth, ballHeight;
    float ballXVel, ballYVel;
    int lastDrawX, lastDrawY;
    int ballSpeed = 10;
    public GamePanel()
    {
        ballX = lastBallX = 100;
        ballY = lastBallY = 100;
        ballWidth = 25;
        ballHeight = 25;
    }

    public void setInterpolation(float interp)
    {
        interpolation = interp;
    }

    public void update()
    {
        lastBallX = ballX;
        lastBallY = ballY;
        ballX += ballXVel;
        ballY += ballYVel;

        //Slowly decreases the ball's velocity
        ballXVel *= 0.975;
        ballYVel *= 0.975;
        //Prints out the velocity
        //////////if(ballXVel > 0.3 && ballYVel > 0.3)
        //////////{
        System.out.println(ballXVel);
        System.out.println(ballYVel);
        //////////}
        //If the ball is at a slow enough velocity, it will stop moving alltogether
        if(ballXVel > 0 && ballYVel > 0)//if x and y velocity are positive
        {
            if(ballXVel < 0.3 && ballYVel < 0.3)
            {
                ballXVel = 0;
                ballYVel = 0;
            }
        }
        if(ballXVel < 0 && ballYVel < 0)//if x and y velocity are negative
        {
            ballXVel *=-1;//Changes to a positive number so it is easier to check
            ballYVel *=-1;//Changes to a positive number so it is easier to check
            if(ballXVel < 0.3 && ballYVel < 0.3)
            {
                ballXVel = 0;
                ballYVel = 0;
            }
            ballXVel *=-1;//Changes the velocity back if it doesn't get set to 0. Even if it does, 0*-1 is still 0
            ballYVel *=-1;//Changes the velocity back if it doesn't get set to 0. Even if it does, 0*-1 is still 0
        }
        if(ballXVel < 0 && ballYVel > 0)//if x velocity is negative and y velocity is positive
        {
            ballXVel *=-1;//Changes to a positive number so it is easier to check
            if(ballXVel < 0.3 && ballYVel < 0.3)
            {
                ballXVel = 0;
                ballYVel = 0;
            }
            ballXVel *=-1;//Changes the velocity back if it doesn't get set to 0. Even if it does, 0*-1 is still 0
        }
        if(ballXVel > 0 && ballYVel < 0)//if x velocity is positive and y velocity is negative
        {
            ballYVel *=-1;//Changes to a positive number so it is easier to check
            if(ballXVel < 0.3 && ballYVel < 0.3)
            {
                ballXVel = 0;
                ballYVel = 0;
            }
            ballYVel *=-1;//Changes the velocity back if it doesn't get set to 0. Even if it does, 0*-1 is still 0
        }

        if(ballX + ballWidth/2 >= getWidth())
        {
            ballXVel *= -0.8;
            ballX = getWidth() - ballWidth/2;
        }
        else if(ballX - ballWidth/2 <= 0)
        {
            ballXVel *= -0.8;
            ballX = ballWidth/2;
        }

        if(ballY + ballHeight/2 >= getHeight())
        {
            ballYVel *= -0.8;
            ballY = getHeight() - ballHeight/2;
        }
        else if(ballY - ballHeight/2 <= 0)
        {
            ballYVel *= -0.8;
            ballY = ballHeight/2;
        }
    }

    public void paintComponent(Graphics g)
    {
        //BS way of clearing out the old rectangle to save CPU.
        g.setColor(getBackground());
        g.fillRect(lastDrawX-1, lastDrawY-1, ballWidth+2, ballHeight+2);
        g.setColor(Color.RED);
        int drawX = (int) ((ballX - lastBallX) * interpolation + lastBallX - ballWidth/2);
        int drawY = (int) ((ballY - lastBallY) * interpolation + lastBallY - ballHeight/2);
        g.fillOval(drawX, drawY, ballWidth, ballHeight);
        lastDrawX = drawX;
        lastDrawY = drawY;
        g.setColor(Color.BLACK);
    }
}
Ortwin Angermeier
  • 5,957
  • 2
  • 34
  • 34

2 Answers2

1

The code you have commented:

//////if(gamePanel.ballXVel == 0 && gamePanel.ballYVel == 0)
//////{
//////    running = !running;
//////}

Put it inside the while(running) loop like:

float EPSILON = 0.00000001f;
if(Math.abs(gamePanel.ballXVel - 0.0f) < EPSILON && Math.abs(gamePanel.ballYVel - 0.0f) < EPSILON)
    {
        running = false;
    }

You have put it outside the while loop. The interpreter will never reach the code because running=true;

EDIT :

I tested the solution myself, it worked. See:

enter image description here

Abdul Fatir
  • 6,159
  • 5
  • 31
  • 58
0

I suggest you change your gamePanel.update() method to return a boolean value, indicating if the marvel is still moving.

You can then use the result of that method to break out of the loop once the marvel has stopped.

In GamePanel#update() something like that.

final float epsilon = 0.00001f;
return (Math.abs(ballXVel) < epsilon) && (Math.abs(ballXVel) < epsilon);

And in Marble#gameLoop something like that:

running = !gamePanel.update();
Ortwin Angermeier
  • 5,957
  • 2
  • 34
  • 34