1

I am trying to implement Conway's Game of Life to teach myself Java. Coming from C/C++ it is quite understandable as well. So far I've been rather successful and (I think) I'm almost done. The code seems to work but there's two things that are still bugging me quite a lot.

  1. I am storing the points to be drawn in a Matrix and painting them using aGraphics.drawLine(i, j, i, j); in the Canvas::paint(Graphics aGraphics) method inside a double for-loop. The problem here is that the build-up of the canvas is visible from left to right. I'm certain that the issue is the repeated call of the drawLine() method, since the build-up is top down when I switch the order of the nested for-loops. Might it be better to store the points in an image?

  2. The other issue is that the game itself runs too quickly. What would be a good idea to suspend the computations of the programme without stopping mid-paint?

Here is my Canvas Class:

class GOLCanvas extends Canvas {
private int m_iWidth;
private int m_iHeight;
private int[][] m_iPoints;
private int[][] m_iNeighbours;
private double m_dSeed;
private Random m_cRnd;

GOLCanvas(int aWidth, int aHeight, double aSeed) {
    m_iWidth = aWidth;
    m_iHeight = aHeight;
    m_dSeed = aSeed;
    m_cRnd = new Random();
    m_cRnd.setSeed(m_cRnd.nextLong());
    m_iPoints = new int[m_iHeight][m_iWidth];
    m_iNeighbours = new int[m_iHeight][m_iWidth];   
}

public void init() {
    // init Points randomly
}

private int getRandomInt(double aProbability) {
    return (m_cRnd.nextDouble() < m_dSeed) ? 1 : 0;
}

public void countNeighbours () {
    // ditto name   
}

public void calcNextStep () {
    // ditto name
}
@Override
public void paint(Graphics aGraphics) {
    // **ANY IDEAS TO SPEED UP THIS PART HERE?**
    for(int i = 0; i < m_iHeight; i++) {
        for(int j = 0; j < m_iWidth; j++) {
            if (m_iPoints[i][j] == 1){
                aGraphics.drawLine(i, j, i, j);
            }
        }
    }
}
David Wright
  • 464
  • 6
  • 18
  • You should probably redesign this, run a `Thread` with a game loop, create an update method(with all the logic) and sync it to N times a second. Also I recommend to create a custom render method with `BufferingStrategy` it will make the game look nicer(remove flickering... etc..) – Dima Maligin Mar 05 '15 at 09:29

2 Answers2

2

Looking at you code I recommend to change the design to a dynamically rendered Canvas, repaint is not implementing multibuffering and may cause flickering. Also regarding your game is running to fast, you need to run your game in your own Thread(not the main Thread) then implement an update method and sync it to N times a second with Thread.sleep.

The design can be something like:

public class Game extends Canvas implements Runnable {

    // resolution
    public static final int     WIDTH         = 640;
    public static final int     HEIGHT        = 480;

    // window title
    private static final String TITLE         = "Title";

    /**
     * Number of logical/physical updates per real second
     */
    private static final int    UPDATE_RATE   = 60;

    /**
     * Number of rendering buffers
     */
    private static final int    BUFFERS_COUNT = 3;

    /**
     * Value of a second in NanoSeconds DO NOT CHANGE!
     */
    private static final long   NANOS_IN_SEC  = 1000000000L;

    /**
     * Update interval in double precision NanoSeconds DO NOT CHANGE!
     */
    private static final double UPDATE_SCALE  = (double) NANOS_IN_SEC / UPDATE_RATE;

    private JFrame              window;
    private Thread              gameThread;
    private boolean             running;

    // temp values 
    int                         x             = 0;
    int                         y             = 0;

    ////////////////

    public Game(JFrame window) {
        this.window = window;
        this.running = false;

        setPreferredSize(new Dimension(WIDTH, HEIGHT));

        this.window.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        // properly ends the game by calling stop when window is closed
        this.window.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                stop();
                super.windowClosing(e);
                System.exit(0);
            }
        });

        this.window.getContentPane().add(this);
        this.window.setResizable(false);
        this.window.pack();
        this.window.setLocationRelativeTo(null);
        this.window.setVisible(true);
    }

    // starts the game
    public synchronized void start() {
        if (running)
            return;

        running = true;
        gameThread = new Thread(this);
        gameThread.start();
        System.out.println("Game thread started");
        System.out.println("UPDATE_RATE: " + UPDATE_RATE);
    }

    // ends the game
    public synchronized void stop() {
        if (!running)
            return;

        running = false;
        boolean retry = true;
        while (retry) {
            try {
                gameThread.join();
                retry = false;
                System.out.println("Game thread stoped");
            } catch (InterruptedException e) {
                System.out.println("Failed sopping game thread, retry in 1 second");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    private void update() {
        // this will run UPDATE_RATE times a second
        x++;
        y++;
    }

    private void render() {
        BufferStrategy bs = getBufferStrategy();
        if (bs == null) {
            createBufferStrategy(BUFFERS_COUNT);
            return;
        }

        Graphics2D g2d = (Graphics2D) bs.getDrawGraphics().create();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        clear(g2d, 0);

        // render here
        g2d.setColor(Color.red);
        g2d.fillRect(x, y, 50, 50);
        //////////////

        g2d.dispose();

        bs.show();
    }

    private void clear(Graphics2D g2d, int shade) {
        g2d.setColor(new Color(shade, shade, shade));
        g2d.fillRect(0, 0, WIDTH, HEIGHT);
    }

    // game loop thread
    public void run() {
        long startTime = System.currentTimeMillis();
        long tick = 1000;

        int upd = 0;
        int fps = 0;

        double updDelta = 0;

        long lastTime = System.nanoTime();

        while (running) {
            long now = System.nanoTime();
            updDelta += (now - lastTime) / UPDATE_SCALE;
            lastTime = now;

            while (updDelta > 1) {
                update();
                upd++;
                updDelta--;
            }
            render();
            fps++;

            if (System.currentTimeMillis() - startTime > tick) {
                window.setTitle(TITLE + " || Upd: " + upd + " | Fps: " + fps);
                upd = 0;
                fps = 0;
                tick += 1000;
            }

            try {
                Thread.sleep(5); // always a good idea to let is breath a bit
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }
}

Usage:

public static void main(String[] args) {
    try {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    EventQueue.invokeLater(() -> {
        new Game(new JFrame()).start();
    });
}

Obviously this is only one way of doing this(there are a lot of other ways), feel free to tweak this to your needs. It took me about 20 minutes to write so I hope it wasn't in vain and this helps, also if you find any mistakes in the code which you cant fix let me know(I kinda wrote it without checking if it works).

Dima Maligin
  • 1,386
  • 2
  • 15
  • 30
  • Thank you very much for your effort. I will try to invoke your changes. I will need to tweak it a little because I used a class that inherits from frame that contains an object of the game canvas class with objects of `Button`, `TextField` , `WindowAdapter` etc. I used the canvas only for calculation and output. But that should not be a problem.This is a little off topic but I see that you use swing classes (JFrame) instead of awt, and since I'm very new to this would you mind telling me what you prefer in swing classes to awt? – David Wright Mar 05 '15 at 12:48
  • @DavidWright I'll just put [this](http://www.withoutbook.com/DifferenceBetweenSubjects.php?subId1=53&subId2=54&d=AWT%20vs%20Swing) here. And you can be the judge your self. – Dima Maligin Mar 05 '15 at 13:33
0

I would check out this link and this one

Basically what it comes down to is using BufferedImages. As far as slowing down a portion of the program, you can use Thread.sleep(1000)where 1000 equals 1 second.

Community
  • 1
  • 1
Dominic Holt
  • 162
  • 1
  • 12