Olá, entusiastas da programação!
Olá, entusiastas da programação! Se você acompanhou nosso último artigo, onde construímos um Jogo da Velha Java Minimax na linha de comando, prepare-se para levar sua experiência a um novo nível. Hoje, vamos transformar aquele código em algo visual e interativo, adicionando uma interface gráfica usando o Swing. Dessa forma, estaremos construindo um completo Jogo da Velha Java Swing Minimax, além de ajustar a dificuldade da IA. Assim, você poderá jogar com o mouse e, inclusive, desafiar o computador em diferentes níveis de dificuldade.
A transição para o gráfico: Swing em ação!
Primeiramente, vamos nos concentrar na criação da interface gráfica. Afinal, ela é a porta de entrada para a interação com nosso jogo. Utilizaremos componentes Swing, como JFrame, JPanel, e JButton, para construir um tabuleiro de jogo amigável e responsivo. Além disso, o jogador poderá interagir diretamente com o tabuleiro usando o mouse, o que torna a experiência mais natural e intuitiva.
Implementando a interface gráfica com Swing
Dessa forma, vamos começar com a criação da classe principal que extenderá JFrame e conterá nosso tabuleiro.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TicTacToeSwing extends JFrame {
private final Board board;
private final AIPlayer aiPlayer;
private final HumanPlayer humanPlayer;
private Player currentPlayer;
private JButton[][] buttons;
private JComboBox<String> difficultyComboBox;
private final int EASY = 1;
private final int MEDIUM = 4;
private final int HARD = 7;
public TicTacToeSwing() {
this.board = new Board();
this.humanPlayer = new HumanPlayer('X');
this.aiPlayer = new AIPlayer('O', humanPlayer.getSymbol());
this.currentPlayer = humanPlayer;
setupUI();
setupDifficultySelection();
}
private void setupUI() {
setTitle("Jogo da Velha");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
setSize(400, 450);
setLocationRelativeTo(null); // Center the window
JPanel gamePanel = new JPanel(new GridLayout(3, 3));
buttons = new JButton[3][3];
// Set up each button
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
JButton button = new JButton("");
button.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 80));
button.addActionListener(new ButtonClickListener(i * 3 + j));
buttons[i][j] = button;
gamePanel.add(button);
}
}
add(gamePanel, BorderLayout.CENTER);
setVisible(true);
}
private void setupDifficultySelection(){
JPanel panel = new JPanel(new FlowLayout());
JLabel difficultyLabel = new JLabel("Nível de dificuldade: ");
String[] difficulties = {"Fácil", "Médio", "Difícil"};
difficultyComboBox = new JComboBox<>(difficulties);
difficultyComboBox.addActionListener(new DifficultyListener());
panel.add(difficultyLabel);
panel.add(difficultyComboBox);
add(panel, BorderLayout.NORTH);
}
private void updateBoard(){
for (int i = 0; i < 3; i++) {
for(int j = 0; j<3; j++){
char symbol = board.getBoard()[i * 3 + j];
buttons[i][j].setText(String.valueOf(symbol));
}
}
if (board.isGameOver()) {
if (board.getWinner() != ' ') {
JOptionPane.showMessageDialog(this, "Jogador " + board.getWinner() + " venceu!");
} else {
JOptionPane.showMessageDialog(this, "Deu empate!");
}
disableAllButtons();
resetBoard();
} else if (currentPlayer instanceof AIPlayer){
aiMove();
}
}
private void aiMove(){
int move = aiPlayer.makeMove(board);
board.makeMove(move, ((AIPlayer) currentPlayer).getSymbol());
currentPlayer = humanPlayer;
updateBoard();
}
private void resetBoard(){
for (int i = 0; i < 3; i++) {
for(int j = 0; j<3; j++){
buttons[i][j].setText("");
buttons[i][j].setEnabled(true);
}
}
board.reset();
currentPlayer = humanPlayer;
}
private void disableAllButtons(){
for (int i = 0; i < 3; i++) {
for(int j = 0; j<3; j++){
buttons[i][j].setEnabled(false);
}
}
}
private class ButtonClickListener implements ActionListener {
private final int position;
public ButtonClickListener(int position) {
this.position = position;
}
@Override
public void actionPerformed(ActionEvent e) {
if (currentPlayer instanceof HumanPlayer && board.isValidMove(position)){
board.makeMove(position, ((HumanPlayer) currentPlayer).getSymbol());
currentPlayer = aiPlayer;
updateBoard();
}
}
}
private class DifficultyListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JComboBox<String> combo = (JComboBox<String>)e.getSource();
String selectedOption = (String)combo.getSelectedItem();
switch (selectedOption){
case "Fácil":
aiPlayer.setMaxDepth(EASY);
break;
case "Médio":
aiPlayer.setMaxDepth(MEDIUM);
break;
case "Difícil":
aiPlayer.setMaxDepth(HARD);
break;
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(TicTacToeSwing::new);
}
}
Alterações na classe AIPlayer para suportar níveis de dificuldade
Agora, para suportar níveis de dificuldade, adicionamos uma variável maxDepth que controla a profundidade de busca do Minimax. Assim, podemos limitar a quantidade de jogadas que a IA prevê no futuro.
A classe Board é refatorada para suportar o reset do jogo
public class Board {
private final char[] board;
private char winner;
public Board() {
this.board = new char[9];
reset();
this.winner = ' ';
}
public void reset(){
for (int i = 0; i < 9; i++) {
board[i] = ' ';
}
this.winner = ' ';
}
public char[] getBoard() {
return board.clone();
}
public char getWinner() {
return winner;
}
public boolean isValidMove(int position) {
return position >= 0 && position < 9 && board[position] == ' ';
}
public void makeMove(int position, char symbol) {
board[position] = symbol;
checkWinner(symbol);
}
public void undoMove(int position) {
board[position] = ' ';
winner = ' ';
}
private void checkWinner(char symbol) {
// Verifica linhas
for (int i = 0; i < 3; i++) {
if (board[i * 3] == symbol && board[i * 3 + 1] == symbol && board[i * 3 + 2] == symbol) {
winner = symbol;
return;
}
}
// Verifica colunas
for (int i = 0; i < 3; i++) {
if (board[i] == symbol && board[i + 3] == symbol && board[i + 6] == symbol) {
winner = symbol;
return;
}
}
// Verifica diagonais
if (board[0] == symbol && board[4] == symbol && board[8] == symbol) {
winner = symbol;
return;
}
if (board[2] == symbol && board[4] == symbol && board[6] == symbol) {
winner = symbol;
return;
}
}
public boolean isGameOver() {
return winner != ' ' || isBoardFull();
}
private boolean isBoardFull() {
for (int i = 0; i < 9; i++) {
if (board[i] == ' ') {
return false;
}
}
return true;
}
}
Executando o jogo
Compile e execute a classe TicTacToeSwing. Uma janela com o tabuleiro do jogo será exibida. Agora, clique nas casas vazias do tabuleiro para fazer suas jogadas e desafiar a IA com diferentes níveis de dificuldade.

Explorando o código e ajustando a Dificuldade
Primeiramente, observe como a classe TicTacToeSwing organiza os componentes do jogo: os botões, o painel, a seleção de dificuldade. A interface foi construída para ser interativa, permitindo que o usuário interaja de forma intuitiva com o jogo. Ademais, você pode perceber como é fácil alterar a profundidade máxima do algoritmo Minimax usando o JComboBox, o que, por conseguinte, torna possível controlar a dificuldade da IA. A classe Board, além disso, foi refatorada para suportar o reset do jogo, tornando o ciclo de gameplay contínuo.
Conclusão
Parabéns! Você transformou seu Jogo da Velha Java Minimax de linha de comando em uma experiência gráfica e interativa usando Swing. Agora, o código está mais robusto, funcional e, principalmente, permite ajustar a dificuldade da IA.
Este projeto ilustra bem como usar os conceitos de orientação a objetos (como os princípios SOLID) e a recursão do Minimax para criar uma aplicação de jogos que, decerto, pode ser aprimorada e expandida para projetos maiores e mais complexos.
Experimente, explore e divirta-se com seus novos conhecimentos! Em resumo, este é um passo importante no caminho da criação de jogos mais complexos e desafiadores.