Accurate Guesser

Java desktop guessing game (GUI).

ROLE
Developer
DOMAIN
Student work

OVERVIEW

Accurate Guesser is a number-guessing game in Java Swing. You guess a random number and get a hint after each try. Fun to build, and also practice in event-driven code, state management and GUI programming in Java.

Game logic and presentation are kept apart, input is checked at several layers, and the UI says clearly what happened. A simple idea, but it teaches the basic patterns.

ARRIVED AS

“Fun UI with simple state machine.”

WHAT I BUILT

  1. 01Swing GUI; input validation; scoring and replay.

WHAT CHANGED

  • Lightweight demo of event-driven UI.

ARCHITECTURE

The application follows a Model-View-Controller (MVC) pattern adapted to Swing's event model. 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. That split means the game logic can be tested without the UI.

Data flow

Game flow begins with initialization where the GameEngine generates a random secret number, resets the attempt count and sets state to PLAYING. 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 counts the attempt, compares the guess 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 the attempt count. 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.

COMPONENTS

  • GameEngine

    Core game logic managing the secret number, guess validation, and scoring

    Handles random number generation within configurable bounds, validates player guesses, maintains attempt counters, and calculates scoring based on accuracy and number of tries. Implements the state transitions and win condition.

  • GameUI (JFrame)

    Main window that holds the other components

    Extends JFrame to create the primary application window. Lays out the input field, feedback labels and action buttons. Handles window lifecycle events and coordinates communication between UI components and game logic.

  • InputPanel

    User input controls for guess submission

    Contains JTextField for numerical input with validation, submit button with ActionListener, and visual feedback for invalid inputs. Implements input filtering to prevent non-numeric entries and enforces range constraints before passing guesses to the game engine.

  • FeedbackDisplay

    Shows hints and game state

    Displays the hints ('Too high!', 'Too low!', 'Correct!'), the attempt count and the current game status.

  • ControlPanel

    Game control actions (new game, reset, exit)

    Provides buttons for starting new games, resetting current game, and exiting the application. Manages state transitions and confirms destructive actions (like resetting mid-game) through JOptionPane dialogs.

Decisions, with the cost of each.

A decision without its trade-off is marketing. Each row says what was chosen, why, and what it gave up.

Swing over JavaFX for UI framework

I picked Swing because it's mature, well documented and easier to learn for GUI basics. JavaFX is more modern, but Swing's simplicity kept the focus on event-driven patterns, not framework abstractions.

AT THE COST OF

Gives up JavaFX's more modern features and look.

State machine for game flow control

Implemented explicit state management (INITIAL, PLAYING, FINISHED) so invalid moves can't happen and behavior stays predictable. This prevents edge cases like submitting guesses after winning or starting multiple games simultaneously. The state machine makes transitions explicit and testable.

AT THE COST OF

More code up front: every state and transition is written out rather than implied.

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. Less coupling, and the player hears about bad input right away.

AT THE COST OF

Checks sit in more than one layer, so changing a rule means changing it in each.

Modal dialogs for game state changes

Used JOptionPane for win notifications and replay confirmations to create clear breakpoints in game flow. A modal dialog makes the player acknowledge the result before moving on, preventing accidental game resets and making state transitions explicit.

AT THE COST OF

Each dialog blocks the window until dismissed: an extra click at every win or replay.

The part that mattered.

The code that did the work.

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());

LEARNED

  1. Event-Driven Architecture Fundamentals

    Swing taught me the basics of event-driven code: listeners keep parts loosely coupled, control flow runs through callbacks, and user input arrives whenever it arrives.

  2. 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. That's why state libraries (Redux, MobX) exist in big apps: tracking state by hand doesn't scale.

  3. Separation of Concerns in Practice

    MVC in Swing showed me why patterns matter. At first I mixed UI code with game logic, and the code was brittle and hard to change. Refactoring to separate GameEngine from GameUI made testing trivial, enabled UI redesigns without touching logic, and clarified responsibilities.

  4. Input Validation Layers

    I learned 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 catches a different kind of failure. One validation point on its own is fragile.

  5. User Experience in Desktop Applications

    Working code isn't the same as good UX. Early versions worked but felt clunky: no visual feedback during input, unclear win conditions, and abrupt state changes. Adding color-coded hints, confirmation dialogs, and immediate input validation made it feel far better. Small details count for more than they look.