Accurate Guesser.
Java desktop guessing game (GUI).
Fun UI with simple state machine.
Accurate Guesser is a classic number guessing game built with Java Swing, designed to demonstrate fundamental GUI programming concepts and event-driven architecture. The player tries to guess a randomly generated number within a range, receiving feedback after each attempt. This project showcases clean separation of concerns between UI presentation, game logic, and state management, all essential principles for desktop application development.
- Swing GUI; input validation; scoring and replay.
Architecture
The application follows a Model-View-Controller (MVC) pattern adapted for Swing's event-driven paradigm. The game state machine manages three primary states: INITIAL (game setup), PLAYING (active guessing), and FINISHED (game over with results). The architecture separates the game logic (random number generation, guess validation, attempt tracking) from the Swing UI components (JFrame, JPanel, JTextField, JButton), connected through ActionListener callbacks. This separation enables easy testing of game logic independently from UI concerns.
GameEngine
Core game logic managing the secret number, guess validation, and scoring
GameUI (JFrame)
Main window container orchestrating all visual components
InputPanel
User input controls for guess submission
FeedbackDisplay
Visual feedback mechanism for game state and hints
ControlPanel
Game control actions (new game, reset, exit)
Data flow
Game flow begins with initialization where the GameEngine generates a random secret number and sets state to INITIAL. When the player enters a guess, the InputPanel validates the input and triggers an ActionEvent. The event handler extracts the guess value, passes it to GameEngine's validateGuess() method, which compares it against the secret number and returns feedback (TOO_HIGH, TOO_LOW, CORRECT). The feedback propagates back to FeedbackDisplay, which updates the UI with appropriate hints and increments the attempt counter. If correct, the game transitions to FINISHED state, displaying win statistics and enabling the replay option. State transitions are atomic and maintain consistency between UI elements and game logic.
Key decisions and trade-offs
Swing over JavaFX for UI framework
Chose Swing for its maturity, extensive documentation, and lower learning curve for fundamental GUI concepts. While JavaFX offers more modern features, Swing's simplicity made it ideal for demonstrating core event-driven programming patterns without framework-specific abstractions.
State machine for game flow control
Implemented explicit state management (INITIAL, PLAYING, FINISHED) to prevent invalid operations and ensure predictable behavior. This prevents edge cases like submitting guesses after winning or starting multiple games simultaneously. The state machine makes transitions explicit and testable.
Input validation at UI layer
Performed validation in InputPanel before reaching GameEngine to provide immediate user feedback and maintain clean separation of concerns. The UI handles presentation-level validation (numeric input, range checks), while GameEngine focuses purely on game logic. This reduces coupling and improves user experience.
Modal dialogs for game state changes
Used JOptionPane for win/loss notifications and replay confirmations to create clear breakpoints in game flow. Modal dialogs ensure users acknowledge outcomes before proceeding, preventing accidental game resets and making state transitions explicit.
Code highlights
public class GameEngine {
private int secretNumber;
private int attempts;
private GameState state;
private final int MIN_VALUE = 1;
private final int MAX_VALUE = 100;
public void startNewGame() {
this.secretNumber = (int)(Math.random() * MAX_VALUE) + MIN_VALUE;
this.attempts = 0;
this.state = GameState.PLAYING;
}
public GuessResult validateGuess(int guess) {
if (state != GameState.PLAYING) {
throw new IllegalStateException("No active game");
}
attempts++;
if (guess == secretNumber) {
state = GameState.FINISHED;
return new GuessResult(ResultType.CORRECT, attempts);
} else if (guess < secretNumber) {
return new GuessResult(ResultType.TOO_LOW, attempts);
} else {
return new GuessResult(ResultType.TOO_HIGH, attempts);
}
}
}
public class GameUI extends JFrame {
private GameEngine engine;
private JTextField guessInput;
private JLabel feedbackLabel;
private JLabel attemptsLabel;
private void initializeListeners() {
submitButton.addActionListener(e -> handleGuessSubmission());
newGameButton.addActionListener(e -> startNewGame());
}
private void handleGuessSubmission() {
try {
int guess = Integer.parseInt(guessInput.getText());
if (guess < 1 || guess > 100) {
showError("Please enter a number between 1 and 100");
return;
}
GuessResult result = engine.validateGuess(guess);
updateFeedback(result);
if (result.getType() == ResultType.CORRECT) {
handleGameWin(result.getAttempts());
}
} catch (NumberFormatException ex) {
showError("Invalid input. Please enter a valid number.");
} catch (IllegalStateException ex) {
showError("Start a new game first!");
}
}
}
// Advanced input validation preventing non-numeric entry
public class NumericDocumentFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset,
String string, AttributeSet attr)
throws BadLocationException {
if (string != null && string.matches("\\d+")) {
super.insertString(fb, offset, string, attr);
}
}
@Override
public void replace(FilterBypass fb, int offset, int length,
String text, AttributeSet attrs)
throws BadLocationException {
if (text != null && text.matches("\\d*")) {
super.replace(fb, offset, length, text, attrs);
}
}
}
// Applied to input field
((AbstractDocument)guessInput.getDocument())
.setDocumentFilter(new NumericDocumentFilter());
- Lightweight demo of event-driven UI.
Event-Driven Architecture Fundamentals
Building with Swing taught the core principles of event-driven programming: loose coupling through listeners, callback-based control flow, and asynchronous user interactions. Understanding how ActionListeners decouple user actions from business logic became foundational knowledge applicable to modern frameworks like React and Vue, where similar event-driven patterns dominate.
State Management Complexity
Even a simple game reveals state management challenges. Without explicit state tracking (INITIAL, PLAYING, FINISHED), edge cases emerged: users submitting guesses before initialization, starting multiple games simultaneously, or continuing after winning. This experience highlighted why state management libraries (Redux, MobX) exist in complex applications, because manual state tracking does not scale.
Separation of Concerns in Practice
Implementing MVC in Swing demonstrated why architectural patterns matter. Initially mixing UI code with game logic created brittle code resistant to changes. Refactoring to separate GameEngine from GameUI made testing trivial, enabled UI redesigns without touching logic, and clarified responsibilities. This lesson applies universally across software development.
Input Validation Layers
Learned to implement defense in depth: UI-level validation for immediate feedback (DocumentFilter for numeric input), application-level validation for business rules (range checking), and domain-level validation in GameEngine (state checks). Each layer serves distinct purposes and prevents different failure modes. Relying on a single validation point creates fragility.
User Experience in Desktop Applications
Discovered that technical correctness doesn't equal good UX. Early versions worked but felt clunky: no visual feedback during input, unclear win conditions, and abrupt state changes. Adding color-coded hints, smooth transitions, confirmation dialogs, and immediate input validation transformed the experience. Small UI details have outsized impact on perceived quality.