2

I am working on a game that my friends invented - a little variation on tic tac toe: using a 4x4 board, one player (x) need to get 3 x's in a particular manner while the other can place one 'z' and one 'o' every turn and needs to fill the entire board. My problem is not with the rules and algoritems, but with the graphics: I don't have a lot of experience with graphics, and just can't get my board to work (even without any rules - just show up as needed).

I have a Board class that represents a board. A board has a two dimensional array of Cells. Each Cell (Cell = another class of mine) is also a JButton, and I would like that every time a button is clicked his image will change - so I decided to use ImageIcon. I also have a GameMain class to control the game and a Tools class to add two buttons - 'Exit' and 'Reset'.

If you could please help me by suggesting ways to get my board to load properly, I would appreciate that. Currently the board doesn't show up at all, and if I tweak the code a bit it shows up but the buttons won't show at all.

Here's the code: GameMain.java:

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class GameMain extends JPanel {
    private Turn _turn;
    Board _board;
    private Tools _buttons;
    private boolean isOver = false;

    public enum GameState {PLAYING, xWON, oWON};

    private GameState _currentState;

    // Name-constants for the various dimensions used for graphics drawing
    public static final int CELL_SIZE = 100; // cell width and height (square)
    public static final int CANVAS_WIDTH = CELL_SIZE * 4;  // the drawing canvas
    public static final int CANVAS_HEIGHT = CELL_SIZE * 4;
    public static final int GRID_WIDTH = 8;  // Grid-line's width
    public static final int GRID_WIDTH_HALF = GRID_WIDTH / 2; // Grid-line's half-width

    public GameMain() {
        this.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                if (_currentState == GameState.PLAYING) {
                    updateGame();
                } else {
                    initGame(); //game over, restart
                }
                repaint();
            }
        });

        setLayout(new BorderLayout());
        setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT + 30));
        _board = new Board();
        _buttons = new Tools();
        initGame();
        _buttons.SetObject(_board);

        add(_board, BorderLayout.CENTER);
        add(_buttons, BorderLayout.SOUTH);
    }

    public void initGame() {
        _turn = Turn.X;
        _board.init();
        _currentState = GameState.PLAYING;
    }

    public void updateGame() {
        if (_board.hasWonX()) {
            _currentState = GameState.xWON;
        } else if (_board.hasWonO()) {
            _currentState = GameState.oWON;
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        setBackground(Color.WHITE);

        _board.paint(g);
    }


    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame("xBlock");
                frame.setSize(500, 500);
                // Set the content-pane of the JFrame to an instance of main JPanel
                frame.setContentPane(new GameMain());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null); // center the application window
                frame.setVisible(true);            // show it
            }
        });
    }
}

Board:

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class Board extends JPanel implements ActionListener {
    private Cell[][] _cells;
    private Turn _turn;

    public Board() {
        setLayout(new GridLayout(4, 4));

        _cells = new Cell[4][4];
        _turn = Turn.X;

        for (int i = 0; i < _cells.length; i++) {
            for (int j = 0; j < _cells[0].length; j++) {
                _cells[i][j] = new Cell(i, j);
                _cells[i][j].addActionListener(this);
                add(_cells[i][j]);
            }
        }
    }

    //initiate board
    public void init() {
        _turn = Turn.X;
        for (int i = 0; i < _cells.length; i++) {
            for (int j = 0; j < _cells[0].length; j++) {
                _cells[i][j].setState(State.EMPTY);
            }
        }

    }

    public void fillCell(Cell c) {
        if (c.getState() == State.EMPTY) {
            c.setState(_turn.ordinal());
            c.setEnabled(false);
            c.draw();
            _turn = _turn.getNext();
        }
    }

    public void checkCellsAround(Cell c) {
        State state = c.getState();
        State right, left, up, down;

        if (c.getJ() < 3 && c.getJ() > 0) {
            right = _cells[c.getI()][c.getJ() + 1].getState();
            left = _cells[c.getI()][c.getJ() - 1].getState();
        } else if (c.getJ() == 0) {
            right = _cells[c.getI()][c.getJ() + 1].getState();
            left = State.EMPTY;
        } else {
            right = State.EMPTY;
            left = _cells[c.getI()][c.getJ() - 1].getState();
        }

        if (c.getI() < 3 && c.getI() > 0) {
            up = _cells[c.getI() - 1][c.getJ()].getState();
            down = _cells[c.getI() + 1][c.getJ()].getState();
        } else if (c.getI() == 0) {
            up = State.EMPTY;
            down = _cells[c.getI() + 1][c.getJ()].getState();
        } else {
            up = _cells[c.getI() - 1][c.getJ()].getState();
            down = State.EMPTY;
        }

        switch (state) {
            case EMPTY:
                break;
            case X:
                if ((left == State.O && right == State.O) || (up == State.O && down == State.O) || (left == State.Z && right == State.Z) || (up == State.Z && down == State.Z)) {
                    c.setState(State.HOURGLASS);
                }
                break;
            case O:
                if ((left == State.X && right == State.X) || (up == State.X && down == State.X)) {
                    c.setState(State.EMPTY);
                }
                break;
            case Z:
                if ((left == State.X && right == State.X) || (up == State.X && down == State.X)) {
                    c.setState(State.HOURGLASS);
                }
                break;
            case HOURGLASS:
                break;
            case SCRIBBLE:
                break;
        }

    }

    public void actionPerformed(ActionEvent E) {
        Cell c = (Cell) E.getSource();
        fillCell(_cells[c.getI()][c.getJ()]);
    }

    public boolean hasWonO() {
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (_cells[i][j].getState() == State.EMPTY) {
                    return false;
                }
            }
        }
        return true;
    }

    public boolean hasWonX() {
        return false;
    }


    public void paint(Graphics g) {
        g.setColor(Color.GRAY);
        for (int i = 1; i < 4; i++) {
            g.fillRoundRect(0, GameMain.CELL_SIZE * i - GameMain.GRID_WIDTH_HALF,
                    GameMain.CANVAS_WIDTH - 1, GameMain.GRID_WIDTH,
                    GameMain.GRID_WIDTH, GameMain.GRID_WIDTH);
        }
        for (int j = 1; j < 4; j++) {
            g.fillRoundRect(GameMain.CELL_SIZE * j - GameMain.GRID_WIDTH_HALF, 0,
                    GameMain.GRID_WIDTH, GameMain.CANVAS_HEIGHT - 1,
                    GameMain.GRID_WIDTH, GameMain.GRID_WIDTH);
        }
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                _cells[i][j].draw();
            }
        }
    }
}

Cell.java:

    import javax.swing.ImageIcon;
import javax.swing.JButton;

public class Cell extends JButton {
    private int _i, _j;
    private State _state;

    ImageIcon X = new ImageIcon(this.getClass().getResource("x-icon.png"));
    ImageIcon O = new ImageIcon(this.getClass().getResource("o-icon.png"));
    ImageIcon Z = new ImageIcon(this.getClass().getResource("z-icon.png"));
    ImageIcon Hourglass = new ImageIcon(this.getClass().getResource("hourglass-icon.png"));
    ImageIcon Scribble = new ImageIcon(this.getClass().getResource("scribble-icon.png"));

    public Cell() {
        this.setEnabled(true);
        _i = 0;
        _j = 0;
        _state = State.EMPTY;

    }

    public Cell(int i, int j) {
        this.setEnabled(true);
        _i = i;
        _j = j;
        _state = State.EMPTY;
    }

    public int getI() {
        return _i;
    }

    public int getJ() {
        return _j;
    }

    public void setState(State state) {
        _state = state;
        if (state == State.EMPTY) {
            this.setEnabled(true);
        }
    }

    public void setState(int index) {
        _state = State.values()[index];
    }

    public State getState() {
        return _state;
    }

    public void draw() {
        switch (_state) {
            case EMPTY:
                this.setIcon(null);
                break;

            case X:
                this.setIcon(X);
                break;

            case O:
                this.setIcon(X);
                break;

            case Z:
                this.setIcon(X);
                break;

            case HOURGLASS:
                this.setIcon(X);
                break;

            case SCRIBBLE:
                this.setIcon(X);
                break;
        }
    }

    public void highlight() {
    }
}

Tools.java:

 import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class Tools extends JPanel {

    private JButton _exit, _reset;
    private Board _board;

    Tools() {
        setLayout(new FlowLayout());

        _exit = new JButton("Exit");
        _reset = new JButton("Reset");

        _exit.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                System.exit(0);
            }
        });

        _reset.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                _board.init();
            }
        });
        add(_exit);
        add(_reset);
    }

    public void SetObject(Board b) {
        _board = b;
    }

}

State.java:

public enum State {
    EMPTY, X, O, Z, HOURGLASS, SCRIBBLE;


    public State getNext() {
        return State.values()[(this.ordinal() + 1) % State.values().length];
    }
}

Turn.java:

public enum Turn {
    X, O, Z;

    public Turn getNext() {
        return Turn.values()[(this.ordinal() + 1) % Turn.values().length];
    }
}

Thanks in advance!

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
Trouble
  • 137
  • 1
  • 4
  • It doesn't look like you're doing anything with the `paintComponent` in the `GameMain`. You should just get rid of the `paintComponent` method altogether in that class. Instead of trying to call `board.paint(g);`, try to just call `board.repaint()` in the mouse listener, instead of trying to repaint the main game panel. – Paul Samsotha Oct 12 '14 at 09:29
  • Also in the `Board` class use `paintComponent` rather than `paint`. and don't forget to call `super.paintComponent` – Paul Samsotha Oct 12 '14 at 09:30
  • Thanks for the quick answer! I changed it, but the panel still won't load! It should be noted that at the moment I care more about getting the board to appear on screen than to make the entire program run as needed, thus some things are not finished (i.e. the wonX returns false because I will later update it, right now I just want the board to load so I can progress). – Trouble Oct 12 '14 at 10:00

1 Answers1

5

So after running it, you are getting an ArrayIndexOutOfBoundsException at this line in the paint method of the Board class:

    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            _cells[i][j].draw();       <==========
        }
    }

Not sure how your game works, but by looking at the loops previous to this one, you are accessing only up to index 3 ( for (int j = 1; j < 4; j++) { ). So if you change the loop max to 4, it gets the game up and running.

    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            _cells[i][j].draw();
        }
    }

Learning to read exceptions and stack traces is very important. It will save you a lot of future headaches. Take some time to read What is a stack trace, and how can I use it to debug my application errors?


And like I said in my comments,

It doesn't look like you're doing anything with the paintComponent in the GameMain. You should just get rid of the paintComponent method altogether in that class. Instead of trying to call board.paint(g);, try to just call board.repaint() in the mouse listener, instead of trying to repaint the main game panel. And just set the background in the constructor of the GameMain instead of in the paintComponent method.

Also in the Board class use paintComponent rather than paint. and don't forget to call super.paintComponent in the paintComponent method

Fixing all the things above, get it work (I guess).


UPDATE

As the MadMan pointed out in the comment below, it would be better to use the _cells.length to avoid having to rely magic numbers. This way you will be sure not to access an inexistant index

for (int i = 0; i < cells.length; i++) {
    for (int j = 0; j < cells[i].length; j++) {
        _cells[i][j].draw();
    }
}
Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • 1
    If you have access to `cells`, wouldn't it be better to use `cells.length` and `cells[i].length`...nit picking ;) – MadProgrammer Oct 12 '14 at 11:00
  • @MadProgrammer yeah. :P – Paul Samsotha Oct 12 '14 at 11:05
  • Oh boy, I feel ashamed for missing that. It's because at first I wanted to make the board 5x5. Thank you guys so much! I fixed it by doing what you said + managed to fix some problems with the ImageIcon (by reading the stack trace) and the board is now up and running - now all that's left is to make the rules work :) – Trouble Oct 12 '14 at 15:03
  • Now I can give a +1 ;) – MadProgrammer Oct 12 '14 at 22:19
  • 1+ but @Trouble is also overriding `paint(...)` and not `paintComponent(...)` and not calling any of the super painting methods, a set up for trouble for Trouble. – Hovercraft Full Of Eels Oct 15 '14 at 21:58