Search code examples
javamultithreadingswingflappy-bird-clone

Troubles with javax.swing.timer while implementing a replica of Flappy Bird in Java


I am writing a replica of Flappy Bird game in Java , but I'm getting stumbled in some stuff concerning threading. Basically,I have 4 classes in my project so far.I have the Renderer class which helps me render the game and look like this:

import java.awt.Graphics;

import javax.swing.JPanel;

public class Renderer extends JPanel
{

    private static final long serialVersionUID = 1L;

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        FlappyBird.flappyBird.repaint(g);
    }

}

I have the Database class which helps me connect to a local hosted database to save the scores and it looks like this:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;  
import java.sql.SQLException;
import java.sql.Statement;

public class Database  {

    private static Connection con;
    private String driverName = "com.mysql.jdbc.Driver";

    public Database(String host, String username, String password){
       try {

            con = DriverManager.getConnection( host, username, password );

           }

       catch (SQLException e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
       }

       try {
            Class.forName(driverName).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            System.err.println(e.getMessage() + "------Cannot Load Driver");

        }
   }

    public void insertRow (Row row)
   {
        try {         

            PreparedStatement stmt = con.prepareStatement("insert into Scores (Username, Score, CoinScore) VALUES (?, ?, ?);");
            stmt.setString(1, row.getUserName());
            stmt.setInt(2, row.getScore());
            stmt.setInt(3, row.getCoinScore());


            stmt.executeUpdate();
        } catch (SQLException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
}

I have a Row class which represent a single row that is about to be inserted in the database and its file looks like :

public class Row {

    private String username;
    private int score;
    private int coinscore;

    public Row(String username, int score, int coinscore)
    {
        this.username = username;
        this.score = score;
        this.coinscore = coinscore;
    }

    public String getUserName()
    {
        return this.username;
    }

    public int getScore()
    {
        return this.score;
    }

    public int getCoinScore()
    {
        return this.coinscore;
    }
} 

And I have the main class named Flappy Bird which is the game itself and looks like this:

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.*;
import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

import javax.swing.JFrame;
import javax.swing.Timer;


public class FlappyBird implements ActionListener, MouseListener, KeyListener
{

    public static FlappyBird flappyBird;

    public final int WIDTH = 800, HEIGHT = 800;

    public Renderer renderer;

    public Rectangle bird;

    public ArrayList<Rectangle> columns,coins;

    public int ticks, yMotion, score, coinScore;

    public boolean gameOver, started;

    public Random rand;

    public Timer timer;

    public FlappyBird()
    {
        JFrame jframe = new JFrame();
        timer = new Timer(50, this);

        renderer = new Renderer();
        rand = new Random();

        jframe.add(renderer);
        jframe.setTitle("Flappy Bird");
        jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jframe.setSize(WIDTH, HEIGHT);
        jframe.addMouseListener(this);
        jframe.addKeyListener(this);
        jframe.setResizable(false);
        jframe.setVisible(true);

        bird = new Rectangle(WIDTH / 2 - 10, HEIGHT / 2 - 10, 20, 20);
        columns = new ArrayList<Rectangle>();
        coins = new ArrayList<Rectangle>();

        addColumn(true);
        addColumn(true);
        addColumn(true);
        addColumn(true);

        timer.start();
    }

    public void addColumn(boolean start)
    {
        int space = 300;
        int width = 100;
        int height = 50 + rand.nextInt(300);

        if (start)
        {
            columns.add(new Rectangle(WIDTH + width + columns.size() * 300, HEIGHT - height - 120, width, height));
            coins.add(new Rectangle(WIDTH+10+columns.size()*300,HEIGHT-height-100,15,15));
            columns.add(new Rectangle(WIDTH + width + (columns.size() - 1) * 300, 0, width, HEIGHT - height - space));
        }
        else
        {
            columns.add(new Rectangle(columns.get(columns.size() - 1).x + 600, HEIGHT - height - 120, width, height));
            coins.add(new Rectangle(columns.get(columns.size()-1).x+rand.nextInt(600) + 100,HEIGHT -height - rand.nextInt(100),15,15));
            columns.add(new Rectangle(columns.get(columns.size() - 1).x, 0, width, HEIGHT - height - space));
        }
    }

    public void paintColumn(Graphics g, Rectangle column)
    {
        g.setColor(Color.green.darker());
        g.fillRect(column.x, column.y, column.width, column.height);
    }

    public void paintCoin(Graphics g, Rectangle coin)
    {
        g.setColor(Color.yellow.darker());
        g.fillRect(coin.x, coin.y, coin.width, coin.height);
    }

    public void paintCoinCyan(Graphics g, Rectangle coin)
    {
        g.setColor(Color.cyan);
        g.fillRect(coin.x, coin.y, coin.width, coin.height);
    }

    public void jump()
    {
        if (gameOver)
        {
            bird = new Rectangle(WIDTH / 2 - 10, HEIGHT / 2 - 10, 20, 20);
            columns.clear();
            coins.clear();
            yMotion = 0;
            score = 0;
            coinScore = 0;

            addColumn(true);
            addColumn(true);
            addColumn(true);
            addColumn(true);
           Scanner in = new Scanner(System.in);
           Database myDB = new Database("jdbc:mysql://localhost:3306/Leaderboard","root","greenlantern10");
           System.out.println("Input your Username: ");
           String user = in.next();
           in.close();

           Row row = new Row(user,score,coinScore);
           myDB.insertRow(row);
            gameOver = false;


        }

        if (!started)
        {
            started = true;
        }
        else if (!gameOver)
        {
            if (yMotion > 0)
            {
                yMotion = 0;
            }

            yMotion -= 10;
        }
    }



    @Override
    public void actionPerformed(ActionEvent e)
    {
        int speed = 15;

        ticks++;

        if (started)
        {
            for (int i = 0; i < columns.size(); i++)
            {
                Rectangle column = columns.get(i);

                column.x -= speed;

            }

            for (int i = 0; i< coins.size();i++)
            {
                Rectangle coin = coins.get(i);

                coin.x -= speed;

            }

            if (ticks % 2 == 0 && yMotion < speed)
            {
                yMotion += 2;
            }

            for (int i = 0; i < columns.size(); i++)
            {
                Rectangle column = columns.get(i);

                if (column.x + column.width < 0)
                {
                    columns.remove(column);

                    if (column.y == 0)
                    {
                        addColumn(false);
                    }
                }
            }

            bird.y += yMotion;

            for (Rectangle column : columns)
            {
                if (column.y == 0 && bird.x + bird.width / 2 > column.x + column.width / 2 - 10 && bird.x + bird.width / 2 < column.x + column.width / 2 + 10)
                {
                    score++;
                }

                if (column.intersects(bird))
                {
                    gameOver = true;

                    if (bird.x <= column.x)
                    {
                        bird.x = column.x - bird.width;

                    }
                    else
                    {
                        if (column.y != 0)
                        {
                            bird.y = column.y - bird.height;
                        }
                        else if (bird.y < column.height)
                        {
                            bird.y = column.height;
                        }
                    }
                }
            }

            for (Rectangle coin : coins)
            {
                if (bird.intersects(coin))
                {
                  coinScore++;

                }
            }

            if (bird.y > HEIGHT - 120)
            {
                bird.y = HEIGHT - 120;
            }

            if (bird.y < 0)
            {
                gameOver = true;
            }

            if (bird.y + yMotion >= HEIGHT - 120)
            {
                bird.y = HEIGHT - 120 - bird.height;
                gameOver = true;
            }
        }

        renderer.repaint();
    }

    public void repaint(Graphics g)
    {
        g.setColor(Color.cyan);
        g.fillRect(0, 0, WIDTH, HEIGHT);

        g.setColor(Color.orange);
        g.fillRect(0, HEIGHT - 120, WIDTH, 120);

        g.setColor(Color.green);
        g.fillRect(0, HEIGHT - 120, WIDTH, 20);

        g.setColor(Color.red);
        g.fillRect(bird.x, bird.y, bird.width, bird.height);

        for (Rectangle column : columns)
        {
            paintColumn(g, column);
        }

        for (Rectangle coin : coins)
        {
            paintCoin(g,coin);
        }

        g.setColor(Color.white);
        g.setFont(new Font("SANS_SERIF", 1, 100));

        if (!started)
        {
            g.drawString("Click or press SPACE to start!", 40, HEIGHT / 2 - 50);
        }

        if (gameOver)
        {
            g.drawString("Game Over!", 200, HEIGHT / 2 - 60);
        }

        if (!gameOver && started)
        {
            g.drawString("Score: "+String.valueOf(score), WIDTH / 2 - 400 , 150);
            g.drawString("Coin score: "+String.valueOf(coinScore),WIDTH / 2- 400, 50);
        }
    }

    public static void main(String[] args)
    {
        flappyBird = new FlappyBird();
    }

    @Override
    public void mouseClicked(MouseEvent e)
    {
        jump();
    }

    @Override
    public void keyReleased(KeyEvent e)
    {
        if (e.getKeyCode() == KeyEvent.VK_SPACE)
        {
            jump();
        }
    }

    @Override
    public void mousePressed(MouseEvent e)
    {
    }

    @Override
    public void mouseReleased(MouseEvent e)
    {
    }

    @Override
    public void mouseEntered(MouseEvent e)
    {
    }

    @Override
    public void mouseExited(MouseEvent e)
    {
    }

    @Override
    public void keyTyped(KeyEvent e)
    {

    }

    @Override
    public void keyPressed(KeyEvent e)
    {

    }

}

The problem arises when I try to insert a new row in the database(When the gameover variable is set i.e. gameover = true) . Apparently, the application makes the insertion in the database properly, but when I try to replay the game I receive some exceptions concerning Event Dispatch Thread and the game no longer goes on. So far, I've tried to stop the timer while I insert the data in the database and then start it over but it didn't worked. What are your opinions, what should I do in order to solve this issue?

P.S: Beside this issue, the game runs without any flaw.

The stack trace:

Exception in thread "AWT-EventQueue-0" java.util.NoSuchElementException
    at java.util.Scanner.throwFor(Unknown Source)
    at java.util.Scanner.next(Unknown Source)
    at flappyBird.FlappyBird.jump(FlappyBird.java:128)
    at flappyBird.FlappyBird.keyReleased(FlappyBird.java:322)
    at java.awt.Component.processKeyEvent(Unknown Source)
    at java.awt.Component.processEvent(Unknown Source)
    at java.awt.Container.processEvent(Unknown Source)
    at java.awt.Window.processEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Window.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.KeyboardFocusManager.redispatchEvent(Unknown Source)
    at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(Unknown Source)
    at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(Unknown Source)
    at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(Unknown Source)
    at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Window.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$500(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)

Solution

  • In the Jump routine, you have misplaced code. It basically attempts to scan for player (name) input within the jump.

    Your logic has you hitting this routine twice, and the second time it is failing. Odds are it is failing due to System.in input that has collected between games.

    Either flush your System.in before querying on it, or better yet, Use the Swing libraries to actually ask for the player name in-game.