📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Sistemas com Java (Swing/JavaFX). Recomendado revisar antes de começar.
—`
simon-java/mvn archetype:generate ou manualmente)Configurar pom.xml com:
maven.compiler.source e target = 21junit-jupiter para testesmaven-surefire-pluginCriar estrutura de pacotes:
org.simon/
├─ model/
├─ view/
├─ controller/
└─ util/
App.javamvn package—`
SimonGame dentro de org.simon.modelImplementar atributos:
List<Integer> sequenceRandom randomboolean strictModeImplementar métodos:
addRandomStep(int maxButtons)validatePrefix(List<Integer> inputSequence)reset() e getSequence() (imutável)setStrictMode e isStrictModeSimonGameTest com JUnit 5Random(0)—`
SimonView em org.simon.viewAdicionar componentes:
JFrameJButton[] colorButtons)Start, Reset, StrictJLabel para pontuaçãoCriar métodos públicos:
show()setStartAction(...), setResetAction(...)setButtonPressedListener(...)updateScore(int)lockButtons(boolean)flashButton(...), flashButtonShort(...)showErrorFeedback() e showSuccessFeedback()SwingUtilities.invokeLaterSimonView.main() provisório—`
SimonController em org.simon.controllerSimonGame e SimonView via construtorImplementar listeners:
view.setStartAction(...) → inicia jogoview.setButtonPressedListener(...) → registra jogadasview.setStrictModeListener(...) → ativa/desativa modo estritoImplementar lógica principal:
start(), reset(), nextRound()playSequence() com ScheduledExecutorServiceonPlayerPress(int index) para validar jogadasSwingUtilities.invokeLater nas interações com UI—`
ScheduledExecutorService para agendamento e Timer para flashesplayerTurn—`
Implementar lógica de erro:
strictMode == true → reiniciar o jogo após errostrictMode == false → repetir sequênciaview.showErrorFeedback() (piscar em vermelho + beep)—`
Criar testes unitários adicionais para SimonGame:
reset()Refatorar código para:
Garantir uso de final e coleções imutáveis onde possível
—`
javax.sound.sampled.Clip)Preferences)—`
mvn testmvn package e rodar java -jar target/simon-java-1.0.0.jarREADME.md com instruções de compilação e execuçãoPreparar slides curtos de apresentação com:
—`
| Ordem | Foco | Entrega Esperada |
|---|---|---|
| 1️⃣ | Estrutura Maven + pacotes | Projeto compila |
| 2️⃣ | Modelo (lógica pura) | SimonGame funcional + testável |
| 3️⃣ | Interface (Swing) | UI visual e interativa |
| 4️⃣ | Controlador | Fluxo completo (rodadas + validação) |
| 5️⃣ | Concorrência (EDT) | UI responsiva |
| 6️⃣ | Modo estrito + feedback | Reações corretas a erros |
| 7️⃣ | Testes e refatoração | Código limpo e coberto |
| 8️⃣ | Extensões opcionais | Sons, níveis, persistência, JavaFX |
| 9️⃣ | Empacotamento e entrega | Jar final + documentação + GitHub |
PT
simon-java/
├─ pom.xml
└─ src/
├─ main/
│ └─ java/org/simon/
│ ├─ App.java
│ ├─ controller/SimonController.java
│ ├─ model/SimonGame.java
│ ├─ view/SimonView.java
│ └─ util/SwingUtils.java
└─ test/
└─ java/org/simon/model/SimonGameTest.java
EN
simon-java/
├─ pom.xml
└─ src/
├─ main/
│ └─ java/org/simon/
│ ├─ App.java
│ ├─ controller/SimonController.java
│ ├─ model/SimonGame.java
│ ├─ view/SimonView.java
│ └─ util/SwingUtils.java
└─ test/
└─ java/org/simon/model/SimonGameTest.java
pom.xml (Maven) — PT / ENPT
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.simon</groupId>
<artifactId>simon-java</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
</plugin>
</plugins>
</build>
</project>
EN
Same pom.xml — Java 21, JUnit for tests.
Segue a implementação organizada por pacotes (modelo, controlador, visão, util). Código pronto para compilar com
mvn packagee executar commvn exec:java(ou executar a classeAppna IDE).
—`
model/SimonGame.java — PT / ENPT (código)
package org.simon.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Modelo puro do jogo Simon (sem UI). Responsável pela sequência, validação e estado básico.
*/
public final class SimonGame {
private final List<Integer> sequence = new ArrayList<>();
private final Random random;
private boolean strictMode = false;
public SimonGame() {
this(new Random());
}
public SimonGame(Random rnd) {
this.random = rnd;
}
public void reset() {
sequence.clear();
}
public int size() {
return sequence.size();
}
public List<Integer> getSequence() {
return List.copyOf(sequence);
}
public int addRandomStep(int maxButtons) {
if (maxButtons <= 0) throw new IllegalArgumentException("maxButtons must be > 0");
int next = random.nextInt(maxButtons);
sequence.add(next);
return next;
}
/**
* Valida o input do jogador passo a passo.
* @param inputSequence lista com os inputs do jogador para comparar com a sequência.
* @return true se todos os inputs conferirem até o tamanho de inputSequence.
*/
public boolean validatePrefix(List<Integer> inputSequence) {
if (inputSequence == null) throw new IllegalArgumentException("inputSequence null");
if (inputSequence.size() > sequence.size()) return false;
for (int i = 0; i < inputSequence.size(); i++) {
if (!sequence.get(i).equals(inputSequence.get(i))) return false;
}
return true;
}
public boolean isStrictMode() {
return strictMode;
}
public void setStrictMode(boolean strictMode) {
this.strictMode = strictMode;
}
}
EN
Same file — pure game model, deterministic via injected Random for testability.
—`
controller/SimonController.java — PT / ENPT
package org.simon.controller;
import org.simon.model.SimonGame;
import org.simon.view.SimonView;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* Controller: coordena fluxo entre modelo e visão.
* Usa ScheduledExecutorService para tocar a sequência sem bloquear a EDT.
*/
public final class SimonController {
private final SimonGame model;
private final SimonView view;
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final ExecutorService uiExecutor = Executors.newSingleThreadExecutor();
private final int maxButtons = 4;
private List<Integer> playerBuffer = new ArrayList<>();
private boolean playerTurn = false;
public SimonController(SimonGame model, SimonView view) {
this.model = model;
this.view = view;
attachViewListeners();
}
private void attachViewListeners() {
view.setStartAction(e -> start());
view.setButtonPressedListener(index -> onPlayerPress(index));
view.setStrictModeListener(on -> model.setStrictMode(on));
view.setResetAction(e -> reset());
}
public void start() {
view.lockButtons(true);
resetRound();
nextRound();
}
private void resetRound() {
playerBuffer.clear();
playerTurn = false;
}
private void nextRound() {
// add step
model.addRandomStep(maxButtons);
view.updateScore(model.size());
playSequence();
}
private void playSequence() {
view.lockButtons(true);
playerBuffer.clear();
List<Integer> seq = model.getSequence();
long delayMs = 600;
long stepDelay = 800;
for (int i = 0; i < seq.size(); i++) {
int idx = seq.get(i);
scheduler.schedule(() -> {
SwingUtilities.invokeLater(() -> view.flashButton(idx));
}, delayMs + i * stepDelay, TimeUnit.MILLISECONDS);
}
// after playing sequence, allow player input
scheduler.schedule(() -> {
playerTurn = true;
SwingUtilities.invokeLater(() -> view.lockButtons(false));
}, delayMs + seq.size() * stepDelay, TimeUnit.MILLISECONDS);
}
private void onPlayerPress(int index) {
if (!playerTurn) return;
view.flashButtonShort(index);
playerBuffer.add(index);
boolean ok = model.validatePrefix(playerBuffer);
if (!ok) {
playerTurn = false;
view.lockButtons(true);
view.showErrorFeedback();
if (model.isStrictMode()) {
// strict: reset game
scheduler.schedule(() -> {
SwingUtilities.invokeLater(() -> {
model.reset();
view.updateScore(0);
start();
});
}, 1000, TimeUnit.MILLISECONDS);
} else {
// show the same sequence again
scheduler.schedule(() -> playSequence(), 1000, TimeUnit.MILLISECONDS);
}
return;
}
// if player completed the full sequence for this round
if (playerBuffer.size() == model.size()) {
playerTurn = false;
view.lockButtons(true);
view.showSuccessFeedback();
scheduler.schedule(this::nextRound, 800, TimeUnit.MILLISECONDS);
}
}
public void reset() {
model.reset();
playerBuffer.clear();
playerTurn = false;
view.updateScore(0);
view.lockButtons(true);
}
public void shutdown() {
scheduler.shutdownNow();
uiExecutor.shutdownNow();
}
}
EN Controller coordinates model and view; schedules playback off EDT and marshals UI updates back to Swing thread.
—`
view/SimonView.java — PT / ENPT
package org.simon.view;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.util.function.IntConsumer;
/**
* Swing UI minimalista: 4 botões coloridos + controles.
* Patterns: view exposes listeners, não lida com lógica do jogo.
*/
public final class SimonView {
private final JFrame frame;
private final JButton[] colorButtons = new JButton[4];
private final JButton startButton = new JButton("Start");
private final JButton resetButton = new JButton("Reset");
private final JLabel scoreLabel = new JLabel("Score: 0");
private final JCheckBox strictCheck = new JCheckBox("Strict");
private IntConsumer buttonPressedListener = i -> {};
private ActionListener startAction = e -> {};
private ActionListener resetAction = e -> {};
private java.util.function.Consumer<Boolean> strictListener = b -> {};
private final Color[] baseColors = {
new Color(0xFF5555), // red
new Color(0x55FF55), // green
new Color(0x5555FF), // blue
new Color(0xFFFF55) // yellow
};
public SimonView() {
frame = new JFrame("Simon - Java 21");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout(8, 8));
JPanel board = new JPanel(new GridLayout(2, 2, 6, 6));
for (int i = 0; i < 4; i++) {
JButton b = new JButton();
b.setOpaque(true);
b.setBorderPainted(false);
b.setBackground(baseColors[i]);
final int idx = i;
b.addActionListener(e -> buttonPressedListener.accept(idx));
colorButtons[i] = b;
board.add(b);
}
board.setPreferredSize(new Dimension(400, 400));
frame.add(board, BorderLayout.CENTER);
JPanel controls = new JPanel();
controls.add(startButton);
controls.add(resetButton);
controls.add(scoreLabel);
controls.add(strictCheck);
frame.add(controls, BorderLayout.SOUTH);
startButton.addActionListener(e -> startAction.actionPerformed(e));
resetButton.addActionListener(e -> resetAction.actionPerformed(e));
strictCheck.addActionListener(e -> strictListener.accept(strictCheck.isSelected()));
frame.pack();
frame.setLocationRelativeTo(null);
}
public void show() {
SwingUtilities.invokeLater(() -> frame.setVisible(true));
}
public void setStartAction(ActionListener act) {
this.startAction = act;
}
public void setResetAction(ActionListener act) {
this.resetAction = act;
}
public void setStrictModeListener(java.util.function.Consumer<Boolean> listener) {
this.strictListener = listener;
}
public void setButtonPressedListener(IntConsumer listener) {
this.buttonPressedListener = listener;
}
public void updateScore(int score) {
SwingUtilities.invokeLater(() -> scoreLabel.setText("Score: " + score));
}
public void lockButtons(boolean locked) {
SwingUtilities.invokeLater(() -> {
for (JButton b : colorButtons) {
b.setEnabled(!locked);
}
});
}
public void flashButton(int index) {
JButton b = colorButtons[index];
Color orig = b.getBackground();
Color bright = orig.brighter();
b.setBackground(bright);
// small delay then restore on EDT
Timer t = new Timer(400, e -> b.setBackground(orig));
t.setRepeats(false);
t.start();
Toolkit.getDefaultToolkit().beep();
}
public void flashButtonShort(int index) {
JButton b = colorButtons[index];
Color orig = b.getBackground();
Color bright = orig.brighter();
b.setBackground(bright);
Timer t = new Timer(140, e -> b.setBackground(orig));
t.setRepeats(false);
t.start();
Toolkit.getDefaultToolkit().beep();
}
public void showErrorFeedback() {
// red flash of frame
SwingUtilities.invokeLater(() -> {
Color orig = frame.getContentPane().getBackground();
frame.getContentPane().setBackground(Color.RED);
Timer t = new Timer(300, e -> frame.getContentPane().setBackground(orig));
t.setRepeats(false);
t.start();
Toolkit.getDefaultToolkit().beep();
});
}
public void showSuccessFeedback() {
SwingUtilities.invokeLater(() -> {
Toolkit.getDefaultToolkit().beep();
});
}
}
EN
View implements Swing UI only; it exposes listeners so controller handles behavior. UI updates are performed on EDT; flash uses Timer.
—`
util/SwingUtils.java — PT / ENPT
package org.simon.util;
import javax.swing.*;
public final class SwingUtils {
private SwingUtils() {}
public static void runOnEDT(Runnable r) {
if (SwingUtilities.isEventDispatchThread()) r.run();
else SwingUtilities.invokeLater(r);
}
}
EN Small helper for ensuring EDT execution.
—`
App.java — PT / ENPT
package org.simon;
import org.simon.controller.SimonController;
import org.simon.model.SimonGame;
import org.simon.view.SimonView;
public class App {
public static void main(String[] args) {
SimonGame model = new SimonGame();
SimonView view = new SimonView();
SimonController controller = new SimonController(model, view);
view.show();
// add shutdown hook to stop schedulers
Runtime.getRuntime().addShutdownHook(new Thread(controller::shutdown));
}
}
EN Bootstrap: creates model, view, controller and shows UI.
—`
test/SimonGameTest.java — PT / ENPT
package org.simon.model;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.*;
class SimonGameTest {
@Test
void addAndValidate() {
// predictable Random
SimonGame g = new SimonGame(new Random(0));
int a = g.addRandomStep(4);
int b = g.addRandomStep(4);
List<Integer> seq = g.getSequence();
assertEquals(2, seq.size());
assertTrue(g.validatePrefix(List.of(seq.get(0))));
assertTrue(g.validatePrefix(seq));
assertFalse(g.validatePrefix(List.of(3,3,3)));
}
}
EN Unit test uses injected Random to guarantee deterministic sequence for assertions.
Observação: cada aula tem objetivo, conteúdo, exercícios práticos e pontos de avaliação.`
PT
Conteúdo:
SimonGame, SimonView, SimonController.Exercício:
validatePrefix.SimonGame para suportar 6 botões (mudar maxButtons no controller para tornar configurável).Avaliação:
EN
PT
Conteúdo:
ScheduledExecutorService, SwingUtilities.invokeLater, javax.swing.Timer).Exercício:
Avaliação:
EN
invokeLater.PT
Conteúdo:
Random para determinismo em testes.strictMode e explicar trade-offs.Exercício:
strictMode (comportamento esperado).Avaliação:
EN
PT
javax.sound.sampled).Preferences).EN
PT
Random no modelo — determinismo para testes.ScheduledExecutorService + SwingUtilities.invokeLater — reproduz sequência sem bloquear UI.getSequence() copia) — evita vazamento de estado.Timer e EDT).EN
Random for deterministic tests.invokeLater to avoid blocking UI.getSequence() returns copy).PT
mvn -DskipTests packagemvn testExecutar (IDE) — executar org.simon.App ou:
mvn exec:java -Dexec.mainClass=org.simon.AppEN
mvn -DskipTests packagemvn testorg.simon.App in IDE or configure maven-exec.PT
Clip.EN
Clip.PT
EN
—`
No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd javadesk_genius_03_sound
javac -d bin src/**/*.java
java -cp bin com.exemplo.Main
# ou mvn test
[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code, basta navegar até a pasta antes de executar: cd proj_aplicacoes_full_stack/projetos/javadesk_genius_03_sound