Search code examples
javaarrays2d-gamesscheduledexecutorservice

Why doesn't this array based Java game work?


I have made a very basic 2D game where you have to shoot down your enemies which are coming from 5 different tracks. I created a 2D array (track) to store the locations of the enemies, projectiles. The array is 790 wide because the track is 790 pixels long. I am using a game loop to update and render which is working just fine.

But because the loop is influenced by the performance of the computer I am using the ScheduledExecutorService class to execute the movement and spawning of enemies but for some reason it isn't working, the enemies don't move or sometimes don't even spawn and the projectiles don't move. The program doesn't give an error it just doesn't work. I checked and there are no syntax errors and I couldn't find any logical problems in it either at least of my knowledge.

Please give a short and not too complicated answer because I'm still a beginner.

The code: (controls are S,D / up, down for movement and space to shoot)

package com.bacskai.peashooter;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;

public class Game extends Canvas implements Runnable, KeyListener {

    private static final long serialVersionUID = -4227990863874935837L;

    JFrame frame;
    static Dimension d;
    Thread thread;

    public static int width = 300;
    public static int height = width / 16 * 9;
    int scale = 3;

    boolean running;
    boolean alive = true;

    int[][] track = new int[5][790]; // the array

    int trackY = 2; // the track which the player is on

    private int playerPos = 250;
    private int playerPos1 = 220; // the 3 starting points for the triangle
    private int playerPos2 = 280;

    int health = 3;
    int score = 0;

    long delay = 100;
    long delay2 = 5000;

    ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    ScheduledExecutorService executor2 = Executors.newScheduledThreadPool(1);



    Runnable task = new Runnable() {

        public void run() {
        move();
        }
    };

    Runnable task2 = new Runnable() {

        public void run() {
            spawnEnemy();
        }
    };

    public Game() {

        d = new Dimension(width * scale, height * scale);
        setPreferredSize(d);
        frame = new JFrame();

    }

    private void start() {

        thread = new Thread(this, "thread");
        running = true;
        thread.start();

        for (int i = 0; i == 5; i++) {
            for (int j = 0; j == 790; j++) { // initializing the array
                track[i][j] = 0;
            }
        }       

        executor.scheduleAtFixedRate(task, delay, delay, 
        TimeUnit.MILLISECONDS); // moveing
        executor2.scheduleAtFixedRate(task2, delay2, delay2, 
        TimeUnit.MILLISECONDS); // spawning new enemies

        System.out.println("Game started, window width: " + getWidth() + ", 
        height: " + 
        getHeight());

    }

    public void run() {

        while (running) { // game loop
            update();
            if (alive) {
            render();
            }
        }

    }

    private void stop() {

        try {

            frame.dispose();
            thread.join();
            executor.shutdownNow();
            executor2.shutdownNow();

        } catch (Exception e) {
            System.out.println("Error while closing: " + e);
        }
        System.out.println("Program closed, processes halted");
    }

    public void update() {

        if (health == 0) {
            alive = false;
            executor.shutdownNow();
            executor2.shutdownNow();
            System.out.println("Game over");

        }

    }

    private void render() {

    BufferStrategy bs = getBufferStrategy();

    if (bs == null) {createBufferStrategy(3); return;}

    Graphics g = bs.getDrawGraphics();

    //  Map

    g.setColor(Color.black);
    g.fillRect(0, 0, getWidth(), getHeight());

    g.setColor(Color.cyan);
    g.fillRect(100, 0, 10, 490);
    g.fillRect(0, 98, 900, 10);
    g.fillRect(0, 196, 900, 10);
    g.fillRect(0, 294, 900, 10);
    g.fillRect(0, 392, 900, 10);

    //  Score / health

    Font font = new Font("Default", Font.PLAIN, 30);
    g.setFont(font);

    g.setColor(Color.red);
    g.drawString("Score: " + score, 740, 30);

    g.setColor(Color.yellow);
    g.drawString("Health: " + health, 600, 30);

    //  Player

    g.setColor(Color.green);
    int[] xPoints = {10, 10, 60};
    int[] yPoints = {playerPos1, playerPos2, playerPos};
    g.fillPolygon(xPoints, yPoints, 3);

    //  Enemies

    g.setColor(Color.red);
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 790; j++) {
            if (track[i][j] == 1) {
                g.fillRect(100 + j, i * 97 + 44, 30, 30);
            }
        }
    }

    //  Projectiles
    g.setColor(Color.green);

    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 790; j++) {
            if (track[i][j] == 2) {
                g.fillOval(110 + j, i * 97 + 44, 27, 27);
            }
        }
    }

    bs.show();
    g.dispose();    
    } // End of render

    public int randInt(int min, int max) {

        Random rand = new Random();
        int randomNum = rand.nextInt((max - min) + 1) + 1;
        return randomNum;

        }

    public void keyTyped(KeyEvent e) {}

    public void keyPressed(KeyEvent e) {

        if(e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            stop();
        }

        if(e.getKeyCode() == KeyEvent.VK_UP && trackY > 0) {
            trackY--;
            playerPos -= 98;
            playerPos1 -= 98;
            playerPos2 -= 98;
            System.out.println("Key pressed: up");
        }

        if(e.getKeyCode() == KeyEvent.VK_DOWN && trackY < 4) {
            trackY++;
            playerPos += 98;
            playerPos1 += 98;
            playerPos2 += 98;
            System.out.println("Key pressed: down");
        }

        if(e.getKeyCode() == KeyEvent.VK_SPACE) {
            shoot();
        }


    }

    public void keyReleased(KeyEvent e) {}

    public void shoot() {

            System.out.println("Player shot projectile from: " + (trackY + 1));
            track[trackY][0] = 2;   

    }

    public void move() {

        System.out.print("asd");
        for (int i = 0; i < 6; i++) {
            for (int j = 0; j < 790; j++) {

                if (track[i][j] == 2 && track[i][j + 2] == 1) {
                    track[i][j] = 0;
                    track[i][j + 2] = 0;
                    break;
                }

                    switch (track[i][j]) {

                        case 0: // 0 ==> empty position

                            break;

                        case 1: // 1 ==> enemy

                            if (j != 0) {

                            track[i][j - 1] = 1;
                            track[i][j] = 0;


                            } else {
                                track[i][j] = 0;
                                enemyArrived();
                            }
                            System.out.print("");
                            break;

                        case 2: // 2 ==> projectile

                            if (j == 789) {
                                track[i][j] = 0;
                                break;
                            }

                            track[i][j + 1] = 2;
                            track[i][j] = 0;
                            System.out.print("");
                            break;

                        default:

                            System.out.println("Unable to identify object type at: track[" + i + "][" + j + "]");
                            break;

                }
            }
        }

    }

    public void spawnEnemy() {

        int trakk = randInt(1, 5);
        track[trakk][789] = 1;

        System.out.println("New enemy at: " + trakk);
    }

    public void enemyArrived() {

        health--;
        System.out.println("Player lost a health point, current health: " + health);

    }


    public static void main(String[] args) {

        Game game = new Game();

        game.frame.setResizable(false);
        game.frame.setTitle("Peasooter");
        game.frame.add(game);
        game.frame.pack();
        game.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        game.frame.setLocationRelativeTo(null);
        game.frame.setVisible(true);
        game.frame.addKeyListener(game);

        game.start();

    }

}

Also I would be very happy if somebody could tell me if there is a way of optimizing this game to not consume 25% - 30% of the cpu.


Solution

  • I've managed to make your app sort of run with some changes and bug fixing. It's not a finished solution but something to help you move forward.

    From the top

    I changed the delays values, this made the game playable since it seems 100 is way to short time. You probably won't like my values for playability but with my values the game can be tested at least.

    long delay = 1000;
    long delay2 = 3000;
    

    No point in having two ScheduledExecutorService objects so delete the second one.

    I have no idea why you want to spawn a thread run your code from there so I removed it.

    //thread = new Thread(this, "thread");
    running = true;
    //thread.start();
    

    And manually called the run() method at the end of start() so with that and the single executor the second half of start()is

        executor.scheduleAtFixedRate(task, delay, delay,
                TimeUnit.MILLISECONDS); // moveing
        executor.scheduleAtFixedRate(task2, delay2, delay2,
                TimeUnit.MILLISECONDS); // spawning new enemies
    
        System.out.println("Game started, window width: " + getWidth() + ", height: " + getHeight());
    
        run();
    }
    

    You are all over the place with your for loops, at one place you have i < 6 for the limit, and at another you have i == 5 (which always will be false). Go through all loops and make sure they are defined as

    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 790; j++) {
    

    Also call randInt properly to fit you array size

    int trakk = randInt(0, 4);
    

    The game still behaves very odd but most of the time I get some red enemies and I can shoot them down.


    Update

    I played some more with it and made two more changes.

    Your random generator method creates a new Random object each time it's called which is unnecessary and I didn't see the point of having the randInt method so I delete the method and declare a new member at the top

    Random rand = new Random();
    

    and then used it where the call to randInt used to be

    int trakk = rand.nextInt(NUMBER_OF_ROWS);
    

    To see if I could improve the performance I introduced 3 constants and then used them for the array and the for loops (replacing all occurrences of 5 and 790 with them)

    private static final int NUMBER_OF_ROWS = 5;
    private static final int NUMBER_OF_PIXELS = 790;
    private static final int LAST_PIXEL = NUMBER_OF_PIXELS - 1;
    

    Then I made the array much smaller by changing the two first to 3 and 100, this again made the game much more playable (although it messed up the graphics some) making it easier to test and fix bugs.