A JavaFX‑based implementation of the classic Snake game built with a layered architecture and a focus on clarity, extensibility, and maintainability.
A quick look at the Snake game in action.
This hubby project is an implementation of the classic Snake game built with JavaFX and structured using a clean layered architecture:
- Domain Layer — the core game model (Snake, Food, Board, Position, Direction)
- Application Layer — the game logic and orchestration (GameController, factories, use cases)
- Presentation Layer — the UI state machine, UI rendering, and input handling (GameView, GameRenderer, menus, UIHelper)
The goal of the project is not only to recreate the Snake game, but to demonstrate how a small interactive application can be built with:
- clear separation of concerns
- predictable state transitions
- immutable value objects
- compositional, easy-to-extend UI components
- testable, framework‑independent domain logic
The codebase emphasizes readability, maintainability, and future‑proof design. It is easy to navigate, easy to extend, and easy to reason about.
Every major part of the system has a single responsibility, making it straightforward to add new features such as additional menus, difficulty modes, or visual effects.
This project uses Maven to build and run the JavaFX Snake game. You only need Java 17+ and Maven installed.
From the project root:
mvn clean installThis compiles the code and installs the program into the local Maven repository.
Use Maven's JavaFX run command:
mvn javafx:runThis launches the JavaFX application and opens the Snake game window.
src/main/java/com/example/snake/
├── app
│ └── MainApp.java // JavaFX entry point; launches the GameView
│
├── config
│ ├── Cfg.java // Global constants (board size, tile size, window size)
│ ├── Difficulty.java // Enum for EASY/NORMAL/HARD with next()/previous() cycling
│ └── DifficultyCfg.java // Per‑difficulty configuration (speed, multipliers, etc.)
│
├── controller // application layer
│ └── GameController.java // Core game orchestration: movement, collisions, scoring, level‑ups
│
├── entities // domain layer
│ ├── Board.java // Board boundaries and utility checks
│ ├── Food.java // Food value + sprite path + position
│ └── Snake.java // Snake body, direction, movement, growth
│
├── factory // application layer
│ ├── GameFactory.java // Factory for full game initialization (state + generator + cfg)
│ └── DifficultyFactory.java // Factory for DifficultyCfg presets for each difficulty
│
├── ui // presentation layer
│ ├── GameLoop.java // Fixed‑rate loop calling GameView.render()
│ ├── GameRenderer.java // Draws gameplay
│ ├── GameView.java // Main UI state machine orchestrating menus + gameplay
│ ├── GameViewState.java // Enum for MENU, RUNNING, PAUSED, GAME_OVER
│ ├── menus
│ │ ├── IMenu.java // Interface for all menus (draw + handleInput)
│ │ ├── GameOverMenu.java // Game‑over screen: draw menu + input handling
│ │ ├── StartMenu.java // Start screen: difficulty selector + input handling
│ │ └── PauseMenu.java // Pause screen: draw menu + input handling
│ └── util
│ └── UIHelper.java // Stateless UI helpers (centering, text measurement, etc.)
│
├── usecases // application layer
│ ├── GameState.java // Use-case DTO: snapshot of the game world (snake, food, score)
│ ├── IFoodGenerator.java // Interface for food generation strategies
│ └── RandomFoodGenerator.java // Random food placement implementation
│
└── vobjects // domain layer: value objects
├── Direction.java // Enum for UP/DOWN/LEFT/RIGHT
└── Position.java // Immutable x/y coordinate value object
The project follows a clean, modular, layered + state‑driven architecture designed for clarity, extensibility, and separation of concerns.
GameView orchestrates the entire UI flow. It decides which part of the game is active based on the current GameViewState:
- MENU → StartMenu
- RUNNING → Gameplay
- PAUSED → Gameplay + overlay
- GAME_OVER → GameOverMenu
It delegates all drawing and input handling to the appropriate component.
A lightweight loop that repeatedly calls:
GameView.render()for drawingGameController.tick()at difficulty‑dependent intervals
This keeps rendering and game logic decoupled and predictable.
Handles all gameplay mechanics:
- Snake movement
- Collision detection
- Food consumption
- Score updates
- Level progression
- Game over conditions
It produces a GameState, a snapshot of the mutable game state used by the renderer.
Responsible for gameplay visuals only:
- Board background
- Snake
- Food
- Score, level, difficulty
Menu classes are responsible for menu rendering.
Each menu implements:
void draw(GraphicsContext gc);
void handleInput(KeyCode code, GameView view);This project is built around a few core principles that keep the codebase clean, modular, and easy to extend. The goal is not just to make a working Snake game, but to create a small, well‑architected UI framework that can grow without becoming tangled.
The GameView acts as a simple but powerful state machine.
Each state (MENU, RUNNING, PAUSED, GAME_OVER) cleanly defines:
- what gets drawn
- what receives input
- what logic runs
This avoids giant if/else blocks and keeps each screen independent.
Gameplay, rendering, and UI screens are intentionally isolated:
GameControllerhandles game logicGameRendererhandles gameplay drawing- Menus handle their own drawing and input
GameVieworchestrates which part is active
Menus implement a simple IMenu interface instead of inheriting from a base class.
Renderers are standalone components.
Shared logic lives in UIHelper.
This keeps the system flexible and avoids deep inheritance chains.
Entities like Position and Direction are immutable value objects.
The game loop is intentionally simple:
GameLooptriggersrender()GameViewdecides what to drawGameControllerupdates the game at fixed intervals
Each class has a narrow purpose:
StartMenuhandles the start screenGameOverMenuhandles the game‑over screenUIHelperhandles layout mathDifficultyhandles cycling logic
This makes the codebase easy to navigate and easy to extend.
The architecture is intentionally designed so new features can be added without refactoring:
- Adding a new menu is straightforward
- Adding new difficulty modes requires no UI changes
- Adding new renderers or UI components is straightforward
The system grows by adding components, not modifying existing ones.
The project includes a focused test suite targeting the core game logic, where correctness and predictability matter most. These tests ensure that the fundamental mechanics behave consistently and remain stable as the project evolves.
-
Position
- immutability
- equality and hashing
- coordinate arithmetic
-
Snake
- movement rules
- growth behavior
- self‑collision detection
- head/tail updates
- direction‑change constraints (no reversing)
-
Board
- boundary checks via
isOutOfBounds - dimension accessors (
rows(),columns())
- boundary checks via
-
Difficulty & DifficultyCfg
- cycling logic (
next(),previous()) - correct preset mapping via
DifficultyFactory - configuration values (tick rate, speed multiplier, scoring thresholds)
- cycling logic (
-
RandomFoodGenerator
- food placement always within board bounds
- avoidance of snake body positions
- valid image path selection
-
GameState
- initial state correctness
- movement and world updates
- scoring and food regeneration
- game‑over conditions (wall collision, self‑collision)
- no updates after game over
These tests focus exclusively on pure logic, avoiding UI or JavaFX dependencies.
- JUnit 5 is used for all unit tests.
Run the full test suite using Maven:
mvn testPlanned improvements and extensions for upcoming versions of the project:
- Introduce per‑player profiles
- Store preferred difficulty, last played mode, and UI settings
- Support multiple local users
- Save high scores and best runs
- Persist game settings (difficulty, controls, theme)
- Store last game state for "continue where you left off"
- Improve menu layout and typography
- Add animations and transitions
- Introduce a theme system (colors, overlays, fonts)
- Refine pause and game‑over screens for better visual clarity
The project is licensed under the MIT License. Refer to license for more information.


