Back to the work
Concept

Accurate Guesser.

Java desktop guessing game (GUI).

JavaSwing
Role Concept
§ 01The problem.

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.

§ 02How it works.
  1. 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.

§ 03Technical deep dive.

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

Game Engine Core Logicjava
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);
        }
    }
}
Swing Event Handler Integrationjava
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!");
        }
    }
}
Input Validation with DocumentFilterjava
// 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());
§ 04Results and impact.
  • Lightweight demo of event-driven UI.
§ 05Key learnings.
  • 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.