InfinityHex is a hexagonal grid-based game built with C# and Avalonia UI. The game features a snake-like gameplay on an infinite hexagonal grid where players navigate through hexagonal blocks.
This will be a fun game to play with a grid of unlimited size. Preformance optimizations will be added later.
Enjoy :)
Author: William Wu
Written in: C#
Last edited: 28/07/2025
Latest release: Game is still in beta
Distributed under the MIT License
Copyright: William Wu © 2025
- Infinite hexagonal grid for gameplay
- Customizable themes via JSON configuration
- Interactive UI with hints system
Cross-platform support via Avalonia UI framework - Keyboard navigation with multiple control schemes
The game is structured into several main components:
- Hex System: Core hexagonal grid implementation with coordinate systems
- Game Engine: Manages game state, movement, and collision detection
- UI Components: Launcher, game interface, and interactive elements
- Keyboard controls: Controling the direction of snake through keys
- Theme System: Customizable appearance through JSON theme files
The hexagonal game engine is the core of the hexagonal snake game, and it handles data processing, transformation, collision checking, and coordinate shifting efficiently using relative coordinate system, relative time system, and multiple delegates to runners for fetching blocks, generating blocks, and converting between absolute and relative time and coordinates.
InfinityHex reuses a specialized hexagonal coordinate system from HappyHex to represent positions on the grid. This system is designed to simplify operations on hexagonal grids, such as movement, adjacency checks, and transformations. The coordinate system includes both raw coordinates and line-based coordinates. These two systems are interdependent and interchangable, although not all raw coordinates map to a integer line coordinate.
Raw Coordinates
- The raw coordinate system uses three axes: I, J, and K.
- These axes run diagonally through the hexagonal grid:
- I+ is 60° to J+.
- J+ is 60° to K+.
- K+ is 60° to I-.
- The relationship between the axes is defined as:
I + J + K = 0 - Each hexagon is represented by a triplet (i, j, k) where one of the values is redundant due to the above constraint.
- Visualization:
Hex Coordinates (i, j, k)
I
/ * (5, 4, -1)
/ * (5, 7, 2)
o - - J
\ * (0, 3, 3)
\
K
Line Coordinates
- Line coordinates are derived from the raw coordinates and represent distances perpendicular to the axes, in contrast to alone the axes.
- The relationships for line coordinates are:
- LineI = (2I + K) / 3
- LineJ = (I - K) / 3
- LineK = (2I + K) / 3
- Line coordinates simplify operations like movement and adjacency checks.
- Visualization:
Line Coordinates (I, J, K)
I
/ * (1, 2, 3)
/ * (3, 1, 4)
o - - J
\ * (2, -1, 1)
\
K
This coordinate system is implemented in the Hex class and extended by other components like Block, which added color property, and HexLib, which is a library that accelerates the handling of directional coordinates that are within one unit distance to the origin. These ensure efficient and intuitive handling of hexagonal grid operations.
To avoid shifting coordinates of all Block references during a snake move, the coordinates are managed by a CoordinateManager, which helps to convert between relative and absolute coordinates.
- Origin: The offset between the relative and absolute system, used to convert between these systems. To avoid too large bound, it is reset to zero periodically.
- Relative Coordinates: These are coordinates relative to the current origin of the grid. They simplify operations like movement and rendering within a localized window.
- Absolute Coordinates: These are global coordinates that represent the actual position of a hex in the infinite grid. They only change during an origin reset.
For the snake, this means that the head of the snake is always at the origin, effectively moving the view grid with the snake head when the snake move, avoiding snake head wandering out of view and ensuring user experience.
The TimedObject<T> class is a thread-safe generic class that associates an object with a time value. It is used to track the age or duration of objects in the game. This is useful for cache management and definition of the snake, especially the tail.
Key features include:
- Thread-Safe Time Manipulation: Methods like
Age(),SetTime(), andRenew()ensure safe concurrent access. - Equality and Comparison: Implements equality and comparison based on both the object and its time value.
- Cloning: Supports shallow cloning for creating copies of timed objects.
This class serves as the foundation for engine cache management by providing time metadata for cache objects.
Similar to the use of Coordinate Manager, the HexEngine also manages time using relative and absolute time to avoid repeated age calls.
- Relative Time: Represents the time difference relative to the current time reference. It is used for operations like aging objects and checking expiration.
- Absolute Time: Represents the global time value, which is reset periodically.
- Expliration: Present as constant in both relative and absolute time. Objects with age greater than this value are considered to be expired.
The TimeReferenceManager handles time conversions and ensures thread-safe operations. It provides methods to age objects, reset the time reference, and check for expiration.
For the snake, time management means that every block of the snake has different time stemp: the head of the snake has time of 0, and the tail of the snake has time equals to the snake length.
Based on time and coordinate managers, the cache is managed efficiently using a LinkedList<TimedObject<Block>>. This cache is used to store recently accessed or generated blocks, ensuring efficient retrieval and management of grid data. This is critical for lazy block creation and unused or old cache collection that supports the infinite grid nature of the game. In later versions, cache management will be progressively improved.
- The engine uses cache search and lazy block creation. The engine searches the cache for blocks using their coordinates. If a block is not found, it is generated with a generator and added to the cache.
- The engine handles cache expiration by removing Blocks that exceed the expiration threshold (managed by
TimeReferenceManager) from the cache. - When the grid is updated (e.g., due to movement or time aging), the cache is adjusted to reflect the new state.
The cache ensures that the engine can efficiently manage a large, infinite grid while maintaining performance and consistency and it will be continue to be updated with new techniques.
The engine implements the IHexPrintable interface, which provides methods for interacting with, updating, and rendering the hexagonal grid in both GUI and ASCII configurations. The GUI build only use the GUI configuration. This interface contains methods to get radius and block array from the engine, checking whether rendering is updated, and more. This interface ensures that the grid can be rendered consistently across different platforms and output formats.
InfinityHex uses Avalonia UI for its graphical interface, ensuring cross-platform compatibility and a responsive design. The application starts with a MainWindow that dynamically loads interactive pages.
To start the game, launch the application, and this will leads you to the Launch Page.
The initial page displayed is the Launch Page, which includes:
- A randomly chosen game hint displayed at the top of the page.
- The current version number prominently shown alongside the hint.
- A Start button centered on the page to begin the game.
- Copyright information displayed at the bottom of the page.
When the Start button is clicked, the application transitions to the Game Page, where players can control the snake using the keyboard. The game page features:
- Real-time gameplay on an infinite hexagonal grid.
- Keyboard controls for navigating the snake.
- A responsive UI that adapts to the player's actions.
- Game data fetched from the backend via the IHexPrintable interface.
Pressing the Escape key at any time during gameplay will return the user to the Launch Page, allowing them to restart or exit the game.
In any time, clicking on the quit button on top of the screen will quit the application.
All configuration are stored in the config directory, which contains a settings JSON file that can be used to configure the default theme to use.
Custom themes can be created in the themes directory using JSON format.
The default theme is stored as Default.json.
You are encouraged to create your own themes by adding json files into the themes directory.
The game supports multiple control schemes:
Left/Decrement: ←, A, Q, U, J
- These keys are used to direct the snake to turn left 60 degrees.
Right/Increment: →, D, E, O, L
- These keys are used to direct the snake to turn right 60 degrees.
No Operation: ↑, W, I
- These keys result in no operation, and will not modify the direction of the snake. In the future, they might be used to change the speed of the game.
Escape: Quit game
- This quits the current game and returns to the main page.