I made a GUI TicTacToe game years ago and wanted to redo it since I now have more programming skills. I was able to shrink the code from 600 lines to around 150 lines.
While I used the same scheme, I ran into some problems that I couldn't solve myself, so please help me out.
The program consists of two classes, the main class TTTMain
:
public class TTTMain {
public static void main(String[] args) {
TTTFrame tttf = new TTTFrame(0,0);
/*Tic Tac Toe Field:
* 0 1 2
* 3 4 5
* 6 7 8
*/
}}
And TTTFrame
:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TTTFrame extends JFrame implements ActionListener {
private Button[] btnPlayButton;
private Button btnRestart;
private int buttonCounter;
private int xScore;
private int oScore;
private Label Olabel, Xlabel;
TTTFrame(int xScore, int oScore) {
this.xScore = xScore;
this.oScore = oScore;
btnPlayButton = new Button[9];
for (int i = 0; i < 9; i++) {
btnPlayButton[i] = new Button("" + i);
btnPlayButton[i].setBackground(Color.white);
btnPlayButton[i].setForeground(Color.white);
btnPlayButton[i].addActionListener(this);
this.add(btnPlayButton[i]);
}
Xlabel = new Label("X: " + this.xScore);
Xlabel.setFont(new Font("Arial", Font.BOLD, 24));
Xlabel.setForeground(Color.white);
Xlabel.setBackground(Color.black);
this.add(Xlabel);
btnRestart = new Button("Restart");
btnRestart.setActionCommand("Restart");
btnRestart.setFont(new Font("Arial", Font.PLAIN, 18));
btnRestart.addActionListener(this);
this.add(btnRestart);
Olabel = new Label("O: " + this.oScore);
Olabel.setFont(new Font("Arial", Font.BOLD, 24));
Olabel.setForeground(Color.white);
Olabel.setBackground(Color.black);
this.add(Olabel);
this.setLayout(new GridLayout(4, 3));
this.pack();
this.setResizable(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("Tic Tac Toe");
this.setSize(300, 400);
this.getContentPane().setBackground(Color.black);
this.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Restart")) {
System.out.println("Restarted");
for (int i = 0; i < 9; i++) {
btnPlayButton[i].setLabel("" + i);
btnPlayButton[i].setForeground(Color.white);
btnPlayButton[i].setBackground(Color.white);
btnPlayButton[i].addActionListener(this);
this.buttonCounter = 0;
}
} else {
((Button) e.getSource()).setFont(new Font("Arial", Font.BOLD, 48));
((Button) e.getSource()).setForeground(Color.black);
System.out.println(buttonCounter);
if (buttonCounter % 2 == 0) {
((Button) e.getSource()).setLabel("X");
((Button) e.getSource()).removeActionListener(this);
} else {
((Button) e.getSource()).setLabel("O");
((Button) e.getSource()).removeActionListener(this);
}
buttonCounter++;
CheckField();
}
}
private void CheckField() {
if (ButtonsWithIdenticalLabels(0, 1, 2)) {
Deactivatebuttons();
}
if (ButtonsWithIdenticalLabels(3, 4, 5)) {
Deactivatebuttons();
}
if (ButtonsWithIdenticalLabels(6, 7, 8)) {
Deactivatebuttons();
}
if (ButtonsWithIdenticalLabels(0, 3, 6)) {
Deactivatebuttons();
}
if (ButtonsWithIdenticalLabels(1, 4, 7)) {
Deactivatebuttons();
}
if (ButtonsWithIdenticalLabels(2, 5, 8)) {
Deactivatebuttons();
}
if (ButtonsWithIdenticalLabels(0, 4, 8)) {
Deactivatebuttons();
}
if (ButtonsWithIdenticalLabels(2, 4, 6)) {
Deactivatebuttons();
}
}
private boolean ButtonsWithIdenticalLabels(int i, int j, int k) {
if (btnPlayButton[i].getLabel() == btnPlayButton[j].getLabel()
&& btnPlayButton[j].getLabel() == btnPlayButton[k].getLabel()) {
btnPlayButton[i].setBackground(Color.red);
btnPlayButton[j].setBackground(Color.red);
btnPlayButton[k].setBackground(Color.red);
if (btnPlayButton[i].getLabel().equals("X")) {
xScore++;
Xlabel.setText("X: " + xScore);
} else {
oScore++;
Olabel.setText("O: " + oScore);
}
return true;
} else {
return false;
}
}
private void Deactivatebuttons() {
for (int i = 0; i < 9; i++) {
btnPlayButton[i].removeActionListener(this);
}
}
}
Now let me explain how the program works. The 3x3 playing field is made of the ButtonArray btnPlayButton
. The buttons are compared by their Labels, so to not have matching labels at the start of the game the buttons are labeled from 1 to 9 right when they are created. Here:
for (int i = 0; i < 9; i++) {
btnPlayButton[i] = new Button("" + i); // Right here
btnPlayButton[i].setBackground(Color.white);
btnPlayButton[i].setForeground(Color.white);
btnPlayButton[i].addActionListener(this);
this.add(btnPlayButton[i]);
}
Whenever you click a btnPlayButton
, the program jumps into the actionPerformed
method. Since the btnPlayButtons
don't have an ActionCommand
, it jumps right into the else section of the method. Here, int buttonCounter
gets greater by 1. Wether buttonCounter
is even or odd, the btnPlayButton that got clicked gets relabeled with "X" or "O". Since buttonCounter
gets +1 with every click, the X and Os are alternating.
Here is said section:
else {
((Button) e.getSource()).setFont(new Font("Arial", Font.BOLD, 48));
((Button) e.getSource()).setForeground(Color.black);
System.out.println(buttonCounter);
if (buttonCounter % 2 == 0) {
((Button) e.getSource()).setLabel("X");
((Button) e.getSource()).removeActionListener(this);
} else {
((Button) e.getSource()).setLabel("O");
((Button) e.getSource()).removeActionListener(this);
}
buttonCounter++;
CheckField();
}
The ActionListener
of the clicked Button are removed to prevent cheating. With every buttonpress, the playing field is checked for a winning combination. This happens in CheckField()
.
In CheckField()
, or to be more precisely, ButtonsWithIdenticalLabels(x, y, z)
the labels of btnPlayButtons[x]
, btnPlayButtons[y]
, btnPlayButtons[z]
are taken and compared, if they are identical it returns true.
Since the btnPlayButton are ordered like this:
0 1 2
3 4 5
6 7 8
the winning combinations are: 012,345,678,036,147,258,045 and 246
so, for example, when btnPlayButton[0]
, btnPlayButton[1]
and btnPlayButton[2]
all have the same label. ButtonsWithIdenticalLabels
is true and the program jumps into Deactivatebuttons()
where all the btnPlayButton
are getting disabled meaning a winning combination was found and the game is over. If the label of btnPlayButton[1]
is "X" then int xScore
gets 1 added to it. Also btnPlayButton[0]
, btnPlayButton[1]
and btnPlayButton[2]
get painted red for aesthetics.
With the Restart button you jump into a for loop that relabels the btnPlayButton again and adds them the ActionListener
that is implemented into TTTFrame. buttonCounter
is getting resetted to 0 as well. The relabeling is the same as the one in the beginning of the class:
if (e.getActionCommand().equals("Restart")) {
System.out.println("Restarted");
for (int i = 0; i < 9; i++) {
btnPlayButton[i].setLabel("" + i);
btnPlayButton[i].setForeground(Color.white);
btnPlayButton[i].setBackground(Color.white);
btnPlayButton[i].addActionListener(this);
this.buttonCounter = 0;
}
Now the problem is that I have is, that after a few restarts, the labeling of X and O isn't alternating anymore. Sometimes there are 3 Os in a row and sometimes even Fields like this are getting recognized as a win
If someone knows how to fix this bug I'd be really happy.
Thanks in advance,
Fihdi
The problem here is: When you restart the game, a new ActionListener
is added to every button. However, it's only removed when you either click it or when someone wins the game. That means when you restart a game before anyone won, every non-clicked button gets a second ActionListener
, so the click will be registered twice and this bug appears. Try to call DeactivateButtons()
before you reset the board.