Um pequeno detalhe que se tornou grande: SudokuSwingApp!
# Introdução
O SudokuSwingApp nasceu como um projeto funcional e direto ao ponto: um tabuleiro de Sudoku com regras clássicas, interface gráfica simples e dois botões — **Verificar** e **Reiniciar**. A proposta inicial era entregar uma experiência básica, porém sólida, para o jogador.
Mas, como acontece com muitos projetos movidos por paixão, um pequeno detalhe acabou mudando tudo.
## A virada inesperada: escolhendo Java Swing em vez do JavaFX
Em vez de migrar para JavaFX, decidi seguir com o **Java Swing**. Sim, aquele mesmo que muitos consideram ULTRAPASSADO! Há... Há... HÁ!
Essa escolha exigiu mais tempo, paciência, criatividade e lógica — especialmente na validação de tarefas e ações.
Por outro lado, me proporcionou liberdade total para construir uma interface personalizada, com controle absoluto sobre cada componente.
O resultado? Uma aplicação que evoluiu de simples para sofisticada, sem perder sua essência.
A busca por melhorias no SudokuSwingApp se tornou constante, com foco na adição de funcionalidades voltadas para **Interação**, **Usabilidade** e **Jogabilidade** — tudo pensado para o **usuário jogador** do tabuleiro Sudoku.
Esses aprimoramentos não apenas tornaram o jogo mais acessível e intuitivo, como também elevaram o nível de imersão e desafio, respeitando o ritmo e estilo de cada jogador.
## O tempo investido para otimizar e obter um melhor desempenho do SudokuSwingApp
Com o tempo, o SudokuSwingApp deixou de ser apenas um jogo. Ele se transformou em um **sistema otimizado**, para obter um melhor desempenho e projetado para oferecer:
🔹 Interação fluida, com navegação por teclado e mouse
🔹 Usabilidade refinada, com suporte a múltiplos idiomas e atalhos inteligentes
🔹 Jogabilidade adaptativa, com quebra-cabeças progressivos e feedback claro ao jogador
Esses pilares redefiniram a experiência do jogador, tornando o SudokuSwingApp mais acessível, envolvente e prazeroso.
## Conclusão
O SudokuSwingApp é a prova de que grandes mudanças podem nascer de pequenos detalhes.
O que começou como um projeto simples virou uma aplicação completa — graças à paixão pela carreira Full-Stack, à persistência e à vontade de oferecer algo melhor para o jogador do tabuleiro Sudoku.
Tudo isso movido por uma força superior e pelo desejo genuíno de criar algo significativo.
# Bootcamp - DIO - Santander 2025 - Back-End com Java
## 🧠 Criando um Jogo do Sudoku em Java
**Projeto de Sudoku com interface gráfica usando Java e Swing.**
A aplicação permite que o usuário jogue Sudoku diretamente em uma interface visual intuitiva, respeitando as regras clássicas do jogo.
## 📦 Requisitos
- Java 17 ou superior
- IDE ou editor de código com suporte a Java (IntelliJ IDEA, Eclipse, VS Code)
## 🛠️ Tecnologias Utilizadas
- Java 21
- Swing (`javax.swing`)
- AWT (`java.awt`)
- Layouts: `GridLayout`, `BorderLayout`
- Entrada com `JTextField` e `DocumentFilter`
- Alertas com `JOptionPane`
- Gerenciamento de status com `GameStatusEnum`
- Argumentos via `args[]` para carregamento de puzzles
## 🚀 Execução da Aplicação
Execute a classe `main.SudokuSwingApp.java` para iniciar a interface gráfica com a Tela de **Abertura** do jogo de Sudoku.
## 🖥️ Modos de Exibição
O **SudokuSwingApp** oferece duas opções de visualização para o usuário jogador:
- 🪟 **Modo Janela:** Inicia com tamanho padrão de **940 x 660 pixels**, podendo ser redimensionado ou maximizado pelo botão da janela
- 🔲 **Modo Tela Cheia (Fullscreen):** Pressione `ALT + ENTER` para alternar entre modo janela e tela cheia, proporcionando uma experiência imersiva
Para **sair do modo Tela Cheia**, o jogador pode pressionar novamente `ALT + ENTER` ou simplesmente apertar a tecla `Esc`, restaurando a interface gráfica para o modo janela.
Esses modos garantem flexibilidade e conforto visual, adaptando-se a diferentes tipos de tela e preferências do jogador.
## 🚪 Sistema SudokuSwingApp
### 🔢 Introdução Animada
Ao iniciar o aplicativo, o usuário é recebido por uma Tela de **Abertura** animada com o ícone do Tabuleiro Sudoku.
Essa introdução proporciona uma experiência visual agradável e profissional, preparando o jogador para acessar o **Menu Principal**.
### 🗂️ Tela do Menu Principal
O Menu Principal do programa contém os seguintes botões:
- **Jogar:** inicia uma nova partida no tabuleiro Sudoku
- **Instruções:** exibe regras e orientações sobre o jogo
- **Configurações:** permite escolher o idioma `Português (Brasil)` ou `Inglês (Estados Unidos)`
- **Sobre:** exibe informações sobre a construção do projeto Sudoku com Java Swing
- **Encerrar:** encerra o aplicativo (`Alt + F4`)
## 🌐 Idiomas disponíveis
O SudokuSwingApp está disponível nos seguintes idiomas:
- 🇧🇷 Português (Brasil)
- 🇺🇸 English (United States)
Na tela de **Configurações**, escolha o idioma que deseja utilizar com o **SudokuSwingApp**.
### ⌨️ Atalhos de Teclado
O SudokuSwingApp oferece suporte para ativação dos botões via teclado, proporcionando uma navegação rápida, acessível e eficiente.
Essa funcionalidade é especialmente útil para usuários que preferem atalhos no teclado em vez do uso do mouse no programa.
###
BR — Português (Brasil) |
US — Inglês (Estados Unidos)
| 📋 Menu Principal (BR) | ⌨️ Atalho | 📋 Main Menu (US) | ⌨️ Shortcut |
|------------------------|------------|-------------------|--------------|
| Jogar | `Alt + J` | Play | `Alt + P` |
| Instruções | `Alt + I` | Instructions | `Alt + I` |
| Configurações | `Alt + C` | Settings | `Alt + S` |
| Sobre | `Alt + S` | About | `Alt + A` |
| Encerrar | `Alt + E` | Exit | `Alt + E` |
## ⚙️ Funcionalidades do Tabuleiro Sudoku
- Interface gráfica com tabuleiro 9×9
- Células fixas e jogáveis
- Atualização do progresso preenchido no rodapé da interface
- Botão **Verificar** habilitado ao preencher todas as células
- Detecção de erros: números repetidos em linhas, colunas e blocos
- Borda vermelha para células com erro após a verificação
- Botão **Solucionar** para revelar a solução correta do quebra-cabeça
- Botão **Novo** que gera sequencialmente um novo puzzle da lista predefinida
- Botão **Reiniciar** que restaura o tabuleiro atual ao seu estado original
| 🧮 Tabuleiro Sudoku (BR) | ⌨️ Atalho | 🧮 Sudoku Board (US) | ⌨️ Shortcut |
|--------------------------|------------|----------------------|--------------|
| Verificar | `Alt + V` | Check | `Alt + C` |
| Solucionar | `Alt + S` | Solve | `Alt + S` |
| Novo | `Alt + N` | New | `Alt + N` |
| Reiniciar | `Alt + R` | Restart | `Alt + R` |
## 🧑💻 Interação com o Tabuleiro
**Modo Teclado:**
- Use `TAB`, `SHIFT+TAB`, `ENTER`, ou setas direcionais para navegar,
- `ARROW UP`, `ARROW DOWN`, `ARROW LEFT`, `ARROW RIGHT`.
- Digite diretamente o número na célula editável.
**Modo Mouse:**
- Botão direito abre menu com números e opção **Limpar**.
- Botão esquerdo confirma o número.
- Clique duplo remove o número inserido.
## 🧩 Mecânica de Jogo
- O sistema oferece **5 puzzles por ciclo**.
- A dificuldade avança automaticamente do nível **iniciante** ao **mestre**.
- Jogabilidade inteligente adaptada ao estilo do jogador.
## 💬 Feedback para o Usuário Jogador
- ❌ Tabuleiro errado:
`Sudoku! Verifique se há números repetidos em linhas, colunas, ou blocos 3x3.`
- ✅ Tabuleiro correto:
`Parabéns! Você completou corretamente o tabuleiro do jogo Sudoku.`
**🎮 Divirta-se resolvendo e dominando o tabuleiro Sudoku!**
**🎯 O desafio é justamente encontrar a combinação correta de números para completar o tabuleiro, seguindo as regras do jogo Sudoku.**
## 🔗 Explore o Projeto Sudoku no GitHub
### Método Principal Main
Este método é responsável por iniciar a execução do programa. Ele define os parâmetros iniciais, chama as funções principais e gerencia o fluxo de controle.
### Estrutura do Código // SudokuSwingApp.java ☕
package main;
import model.Board;
import model.BoardTemplate;
import model.InvalidBoardFormatException;
import model.PuzzleRepository;
import util.SudokuLanguage;
import util.SudokuSettings;
import view.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import java.util.Locale;
public class SudokuSwingApp {
private static JFrame menuFrame;
private static JFrame currentFrame;
private static boolean isFullScreen;
private static Rectangle windowBounds;
private static final GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
private static boolean shortcutsRegistered = false;
private static int lastExtendedState = JFrame.NORMAL;
public static void main(String[] args) {
setLanguage(new Locale("pt", "BR"));
new SudokuSplash().showSplash(5000);
SudokuStyle.applyGlobalStyle();
SudokuSound.initSounds();
SwingUtilities.invokeLater(SudokuSwingApp::showMainMenu);
}
public static void setLanguage(Locale locale) {
SudokuLanguage.setLocale(locale);
if (currentFrame != null) updateFrameLanguage(currentFrame);
}
private static void updateFrameLanguage(JFrame frame) {
Container content = frame.getContentPane();
if (content instanceof SudokuMenu) {
frame.setContentPane(new SudokuMenu());
} else if (content instanceof SudokuSettings) {
frame.setContentPane(new SudokuSettings());
} else if (content instanceof SudokuInstructions) {
frame.setContentPane(new SudokuInstructions());
} else if (content instanceof SudokuPanel) {
Board board = ((SudokuPanel) content).getBoard();
frame.setContentPane(new SudokuPanel(board));
} else if (content instanceof SudokuAbout) {
frame.setContentPane(new SudokuAbout());
}
bindClickSoundToButtons(frame.getContentPane());
frame.revalidate();
frame.repaint();
}
public static void playClickSound() {
SudokuSound.playClickSound();
}
private static void bindClickSoundToButtons(Container container) {
for (Component comp : container.getComponents()) {
if (comp instanceof JButton button) {
ActionListener[] listeners = button.getActionListeners();
for (ActionListener listener : listeners) {
button.removeActionListener(listener);
button.addActionListener(e -> {
playClickSound();
listener.actionPerformed(e);
});
}
} else if (comp instanceof Container child) {
bindClickSoundToButtons(child);
}
}
}
private static void showMainMenu() {
if (menuFrame != null) menuFrame.dispose();
menuFrame = new JFrame("Sudoku");
currentFrame = menuFrame;
menuFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
menuFrame.setLayout(new BorderLayout());
applyAppIcon(menuFrame);
menuFrame.getContentPane().setBackground(SudokuStyle.COLOR_PRIMARY);
menuFrame.setContentPane(new SudokuMenu());
bindClickSoundToButtons(menuFrame.getContentPane());
applyPreviousWindowState(menuFrame);
applyFullscreenIfActive(menuFrame);
menuFrame.setVisible(true);
menuFrame.requestFocus();
menuFrame.toFront();
setupWindowShortcuts(menuFrame);
}
public static void startGame() {
Board board;
try {
board = BoardTemplate.of(PuzzleRepository.getPuzzleInicial()).build();
board.getSpaces().forEach(row ->
row.stream()
.filter(space -> !space.isFixed())
.forEach(space -> space.setActual(null))
);
} catch (InvalidBoardFormatException e) {
JOptionPane.showMessageDialog(null, "Erro ao carregar o puzzle.", "Erro", JOptionPane.ERROR_MESSAGE);
return;
}
showFrameWithContent("Sudoku", new SudokuPanel(board), SudokuSwingApp::showMainMenu);
}
private static void showFrameWithContent(String title, JComponent content, Runnable onClose) {
int previousState = currentFrame != null ? currentFrame.getExtendedState() : JFrame.NORMAL;
boolean wasFullScreen = isFullScreen;
if (currentFrame != null) currentFrame.dispose();
JFrame frame = new JFrame(title);
currentFrame = frame;
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
frame.dispose();
if (onClose != null) onClose.run();
}
});
applyAppIcon(frame);
frame.setLayout(new BorderLayout());
frame.getContentPane().setBackground(SudokuStyle.COLOR_PRIMARY);
frame.setContentPane(content);
bindClickSoundToButtons(frame.getContentPane());
if (wasFullScreen) {
applyFullscreenIfActive(frame);
} else {
frame.setUndecorated(false);
frame.setExtendedState(previousState);
if (previousState == JFrame.NORMAL) {
frame.setSize(new Dimension(940, 660));
frame.setLocationRelativeTo(null);
}
}
frame.setVisible(true);
setupWindowShortcuts(frame);
frame.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (frame.getExtendedState() == JFrame.NORMAL) {
Dimension size = frame.getSize();
if (size.width < 940 || size.height < 660) {
frame.setSize(940, 660);
frame.setLocationRelativeTo(null);
}
}
}
});
}
public static void showInstructions() {
showFrameWithContent("Sudoku", new SudokuInstructions(), () -> reopenMainMenu(currentFrame));
}
public static void showSettings() {
showFrameWithContent("Sudoku", new SudokuSettings(), () -> reopenMainMenu(currentFrame));
}
public static void showAbout() {
showFrameWithContent("Sudoku", new SudokuAbout(), () -> reopenMainMenu(currentFrame));
}
public static void reopenMainMenu(JFrame closingFrame) {
saveWindowState(closingFrame);
closingFrame.dispose();
showMainMenu();
}
public static void applyAppIcon(JFrame frame) {
URL iconUrl = SudokuSwingApp.class.getResource("/images/icons/sudoku_icon.png");
if (iconUrl != null) {
Image image = new ImageIcon(iconUrl).getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH);
frame.setIconImage(image);
}
}
private static void setupWindowShortcuts(JFrame frame) {
if (shortcutsRegistered) return;
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(e -> {
if (e.getID() == KeyEvent.KEY_PRESSED && currentFrame != null) {
if (e.isAltDown() && e.getKeyCode() == KeyEvent.VK_ENTER) {
toggleFullscreen(currentFrame);
return true;
}
if (e.getKeyCode() == KeyEvent.VK_ESCAPE && isFullScreen) {
exitFullscreen(currentFrame);
return true;
}
if (e.isControlDown()) {
if (e.getKeyCode() == KeyEvent.VK_1) {
applyWindowedMode(currentFrame);
return true;
}
if (e.getKeyCode() == KeyEvent.VK_2) {
applyMaximizedMode(currentFrame);
return true;
}
}
}
return false;
});
shortcutsRegistered = true;
}
private static void applyWindowedMode(JFrame frame) {
if (isFullScreen) {
gd.setFullScreenWindow(null);
isFullScreen = false;
}
frame.dispose();
frame.setUndecorated(false);
lastExtendedState = JFrame.NORMAL;
applyPreviousWindowState(frame);
frame.setVisible(true);
}
private static void applyMaximizedMode(JFrame frame) {
if (isFullScreen) {
gd.setFullScreenWindow(null);
isFullScreen = false;
}
frame.dispose();
frame.setUndecorated(false);
lastExtendedState = JFrame.MAXIMIZED_BOTH;
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setVisible(true);
}
private static void toggleFullscreen(JFrame frame) {
if (!isFullScreen && gd.isFullScreenSupported()) {
saveWindowState(frame);
frame.dispose();
frame.setUndecorated(true);
gd.setFullScreenWindow(frame);
frame.setVisible(true);
isFullScreen = true;
} else {
exitFullscreen(frame);
}
}
private static void exitFullscreen(JFrame frame) {
gd.setFullScreenWindow(null);
isFullScreen = false;
frame.dispose();
frame.setUndecorated(false);
frame.setSize(new Dimension(940, 660));
frame.setLocationRelativeTo(null);
frame.setExtendedState(JFrame.NORMAL);
frame.setVisible(true);
}
private static void applyFullscreenIfActive(JFrame frame) {
if (isFullScreen && gd.isFullScreenSupported()) {
frame.dispose();
frame.setUndecorated(true);
gd.setFullScreenWindow(frame);
frame.setVisible(true);
}
}
private static void saveWindowState(JFrame frame) {
if (!isFullScreen) {
windowBounds = frame.getBounds();
lastExtendedState = frame.getExtendedState();
}
}
private static void applyPreviousWindowState(JFrame frame) {
if (!isFullScreen && windowBounds != null) {
frame.setBounds(windowBounds);
frame.setExtendedState(lastExtendedState);
} else {
frame.setSize(new Dimension(940, 660));
frame.setLocationRelativeTo(null);
frame.setExtendedState(JFrame.NORMAL);
}
}
}