Skip to content

cjarju/snake-game

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Snake Game

A JavaFX‑based implementation of the classic Snake game built with a layered architecture and a focus on clarity, extensibility, and maintainability.

Demo

A quick look at the Snake game in action.

Start Menu

Pause Screen

Game Over Screen

About project

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.

Usage

This project uses Maven to build and run the JavaFX Snake game. You only need Java 17+ and Maven installed.

Build the project

From the project root:

mvn clean install

This compiles the code and installs the program into the local Maven repository.

Run the game

Use Maven's JavaFX run command:

mvn javafx:run

This launches the JavaFX application and opens the Snake game window.

Project Structure

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

Architecture Overview

The project follows a clean, modular, layered + state‑driven architecture designed for clarity, extensibility, and separation of concerns.

GameView — The UI State Machine

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.

GameLoop — The Render/Tick Driver

A lightweight loop that repeatedly calls:

  • GameView.render() for drawing
  • GameController.tick() at difficulty‑dependent intervals

This keeps rendering and game logic decoupled and predictable.

GameController — Core Gameplay Logic

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.

GameRenderer — Visual Representation of Gameplay

Responsible for gameplay visuals only:

  • Board background
  • Snake
  • Food
  • Score, level, difficulty

Menus — Independent UI Screens

Menu classes are responsible for menu rendering.

Each menu implements:

void draw(GraphicsContext gc);
void handleInput(KeyCode code, GameView view);

Design Philosophy

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.

State‑Driven UI

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.

Separation of Concerns

Gameplay, rendering, and UI screens are intentionally isolated:

  • GameController handles game logic
  • GameRenderer handles gameplay drawing
  • Menus handle their own drawing and input
  • GameView orchestrates which part is active

Composition Over Inheritance

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.

Pure, Immutable Data Structures

Entities like Position and Direction are immutable value objects.

Explicit, Predictable Flow

The game loop is intentionally simple:

  • GameLoop triggers render()
  • GameView decides what to draw
  • GameController updates the game at fixed intervals

Small, Focused Components

Each class has a narrow purpose:

  • StartMenu handles the start screen
  • GameOverMenu handles the game‑over screen
  • UIHelper handles layout math
  • Difficulty handles cycling logic

This makes the codebase easy to navigate and easy to extend.

Future‑Proof Extensibility

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.


Test Suite

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.

What is tested

  • 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())
  • Difficulty & DifficultyCfg

    • cycling logic (next(), previous())
    • correct preset mapping via DifficultyFactory
    • configuration values (tick rate, speed multiplier, scoring thresholds)
  • 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.

Test Framework

  • JUnit 5 is used for all unit tests.

Running Tests

Run the full test suite using Maven:

mvn test

Future Work

Planned improvements and extensions for upcoming versions of the project:

User Profile System

  • Introduce per‑player profiles
  • Store preferred difficulty, last played mode, and UI settings
  • Support multiple local users

Persistence (SQLite)

  • Save high scores and best runs
  • Persist game settings (difficulty, controls, theme)
  • Store last game state for "continue where you left off"

UI & Styling Enhancements

  • 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

License

The project is licensed under the MIT License. Refer to license for more information.

About

Classic snake game in JavaFX

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages