During the preparation of this work, the authors used GitHub Copilot and DeepL to assist with writing the Javadoc and to check the grammar of this report. Moreover, Copilot has been used as a review tool in some pull requests on GitHub. After using this tool/service, the authors reviewed and edited the content as needed and take(s) full responsibility for the content of the final report/artifact.
This report presents the “Donkey Kong: Rush” project, realized for the “Distributed System” course at the University of Bologna. The project involves the development of a 2D multiplayer platform video game inspired by the classic Donkey Kong. The system is designed around an Entity-Component-System (ECS) architecture written in Java, utilizing JavaFX for rendering. The main focus of the project is the implementation of smooth multiplayer gameplay. The solution combines the Host’s authority over the deterministic environment with a reactive handling of local inputs, guaranteeing an experience free of blocking lag for the players.
“Donkey Kong: Rush” is a desktop application featuring a graphical user interface (GUI). Specifically, it is a 2D multiplayer platform video game, which takes inspiration from the original Donkey Kong arcade game.
The software provides a competitive multiplayer platforming experience. Users are located on separate desktop machines connected over the same local area network. Thus, distribution is a fundamental requirement for this project to enable a seamless multiplayer experience, as it allows players on physically separate machines across a network to connect, interact, and share the same virtual space.
Interaction is continuous and in real-time during an active game session. The system captures inputs and exchanges state updates across the network multiple times per second to maintain synchronization.
Players interact with the system via the GUI during setup or keyboard controls during the game. The game controls consist of standard keybindings for movement (left/right, climbing ladders) and jumping.
The system does not require persistent, long-term data storage. All necessary data represents the state of the ongoing match (such as player and barrel coordinates) and is kept strictly in RAM.
The game relies on multiple user roles:
| Term | Definition |
|---|---|
| Host | A player who actively plays the game, processes local input, acts as the authoritative entity for game-world generation (e.g., barrels), and broadcasts the state to clients. |
| Guest | A player who actively plays the game, processes local input, and receives continuous world state updates from the Host. |
| Spectator | A passive user who does not generate input but receives real-time updates to render and watch the match. |
| Lobby | The pre-game networking state where users connect and are assigned their respective roles (Host, Guest, or Spectator) before the match begins. |
| Entity | Any distinct object in the game world. |
| Barrel | A dynamic entity that deals damage when a player comes into contact with it. |
| Ladder | A climbable entity in the game level that enables players to move vertically regardless of gravity. |
| Description | Acceptance Criterion | Â |
|---|---|---|
| The system must allow users to join a lobby and automatically assign them a role (Host, Guest, or Spectator) based on join order or preference. | A user successfully connects to the server and receives a distinct role assignment (Host, Guest, or Spectator) before the game starts. | Â |
| The system must allow active players to move horizontally (left/right), jump, and climb ladders. | Pressing the designated keys updates the player’s position appropriately on the screen according to game physics (gravity, collision). |  |
| The system must synchronize the game state (entities positions, and lives) between all connected clients in real time. | When the Host moves or a barrel spawns, the Guest and Spectators see the updated positions on their screens without noticeable desynchronization. | Â |
| The system must detect collisions between players and damaging entities (e.g., barrels), deducting a life upon impact. | When a player’s character intersects with a barrel, the player’s life count decreases by 1, and the character respawns at the starting position. |  |
| The system must declare a winner if a player reaches the goal (Pauline) or declare a loser if a player’s lives reach zero. | The game transitions to a “Game Over” screen displaying the correct winner or loser when the goal is touched or 3 lives are lost. |  |
These requirements define the behavioral aspects and quality attributes of the system.
| Description | Acceptance Criterion |
|---|---|
| The game must update and render at a consistent frame rate to ensure smooth gameplay. | The local game loop executes consistently at the target of 60 Frames-Per-Second (FPS). |
| State updates must be transmitted rapidly to prevent visual stuttering or unfair advantages. | State update payloads are serialized, transmitted over the local network, and deserialized by the receiving client in under 33 milliseconds (roughly 2 frames). |
| The system must ensure automatic recovery from disconnections for non-Host users. | If a Guest or Spectator loses their network connection and subsequently reconnects to the server while the match is still active, the system automatically restores their role and resumes sending them real-time game state updates without requiring a full lobby reset. |
| Code must be modular and documented to make extensions and changes easy. | Modularity is enforced by strictly decoupling data structures from execution logic, avoiding deep and rigid inheritance trees. Maintainability is verified by the presence of comprehensive Javadoc comments on all core API interfaces, ensuring new features can be integrated rapidly. |
Performance, concurrency, and communication efficiency: Real-time games require extremely strict throughput and response times. The system must process player inputs, update physics, and broadcast network messages concurrently without blocking the main application thread. High communication efficiency is paramount to ensure state updates are delivered within the 33ms latency threshold to maintain a fair 60 FPS gameplay experience.
Evolvability and maintainability: To ensure long-term maintainability and ease of updates, the system’s architecture must strictly decouple game state data from execution logic. The design must allow new game mechanics or virtual objects to be added modularly, actively avoiding rigid, monolithic class hierarchies.
Fault tolerance, dependability, and availability: While data integrity for long-term storage is irrelevant since all match state is kept in volatile memory, the system must gracefully handle network faults. If a player’s connection drops, the central server must detect the failure and immediately broadcast a game-over message, resetting the lobby to prevent deadlocks and ensure continued availability for future matches.
Resource sharing: The active game world acts as a synchronized shared resource. The Host is responsible for managing authoritative game mechanics and synchronizing this shared state with the Guest and Spectators via continuous network message broadcasts.
Transparency: The system provides basic location transparency; clients seamlessly connect to the lobby without needing to know the physical network topology of the other players. However, failure transparency is intentionally absent; if a connection drops, the failure is explicitly exposed to the remaining users via the game over UI.
Scalability: The game is explicitly bounded to a small, finite number of simultaneous connections (one Host, one Guest, and passive Spectators) on a local area network. It is not expected to scale horizontally to thousands of users or handle massive data growth over time.
Security and trust: As a local LAN game without persistent user accounts or sensitive data storage, there is no need for cryptographic schemes, data encryption, or complex authentication mechanisms.
Openness and interoperability: The project is a closed ecosystem. While it utilizes standardized text formats for message passing, it is not required to interact with external third-party systems or heterogeneous technological components.
Economy and costs: Developed as an academic project running on local hardware, there are no cloud deployment budgets, operational costs, or strict economic constraints driving the architecture.
This chapter explains the strategies used to meet the requirements identified in the analysis.
The project follows the Model-View-Controller (MVC) and Entity-Component-System (ECS) architectural patterns. The MVC pattern is used to separate the user interface from the game logic, while the ECS pattern is used to manage the game entities and their behaviors.
For what concerns the distributed aspect of the project, a client-server architecture is used. The host player act as both the client and the server, while the other player and any spectators act as clients. The server is responsible for managing the game state and broadcasting updates to all connected clients.
This architecture was selected for its simplicity, efficiency, low latency, and ability to abstract complexity from the user. A decentralized peer-to-peer alternative, where each user operates an interconnected server, was evaluated but rejected due to high implementation complexity and network latency overhead. Its sole advantage, mitigating the single point of failure if a central leader goes offline, did not justify the operational trade-offs.
This section details the architectural and structural decisions made to fulfill the system’s requirements.
The project follows the Model-View-Controller (MVC) and Entity-Component-System (ECS) architectural patterns. The MVC pattern is used to separate the User Interface (UI) from the game logic, while the ECS pattern is used to manage the game entities and their behaviors.
Regarding its distributed nature, the system utilizes a centralized Client-Server architecture heavily reliant on an Event-Driven model. The server acts as a central authoritative game lobby and message relay, while the clients maintain local instances of the game. This design minimizes peer-to-peer connection issues and provides a single source of truth for matchmaking, role assignment, and fault management.
To support the game’s multiplayer requirements, the infrastructure relies on a centralized star topology where all network traffic flows through the main server.
LobbyVerticle): A single server component responsible for accepting connections, storing the
active socket states (one hostSocket, one guestSocket, and a list of spectators), and brokering messages.ClientVerticle): Instances running on the players’ local machines that establish a WebSocketClient
connection to the server.HostUpdateMessage, it sends the data (including player coordinates, state, lives, and barrel data) to
the server, which then mirrors this data to the Guest and all Spectators.hostIp) and port (8080), configured
upon client initialization.HOST role to the first connection and the GUEST role to the second.
Observers use a distinct discovery mechanism by connecting to the specific URI path /spectate, which routes them
directly into the server’s spectators pool.
World (the container of the game state), the Player (representing either the Host or Guest avatars), the Barrel (dynamic obstacles), and static elements like Ladder and Platform.LobbyVerticle (Server) and the ClientVerticle (Clients) maintain representations of the game state.
WorldImpl instance, processing physics and rendering the GUI. The Host generates authoritative data (e.g., barrel spawns) and maps this to the Server via events.GAME_START (which initializes the simulation on all nodes), HOST_UPDATE (transmitting the Host’s position and global entity data), GUEST_UPDATE (transmitting the Guest’s position), and terminal events like GOAL_REACHED, PLAYER_DIED, or GUEST_DISCONNECTED.
WebSocket connection. The ClientVerticle pushes messages onto the local Vert.x EventBus, which are then transmitted over the network to the LobbyVerticle. The Server broadcasts these messages back to the appropriate endpoints (either to the opposing player or to all Spectators).HostUpdateMessage and GuestUpdateMessage) are fired iteratively during the GameLoop (running at a target of 60 frames per second). Event-triggered messages (like ENTITY_DESTROYED) are communicated asynchronously only when a specific collision resolves locally.ROLE_ASSIGNMENT.
SpawnSystem). It then blindly pushes this definitive state outwards.HOST_UPDATE message via the StateReceiverSystem.World based on elapsed time (deltaTime), but strictly overwrites entity positions based on incoming network messages to render the current frame.AnimationTimer, the WorldImpl.update() method iterates through all active GameSystems (e.g., MovementSystem, PhysicsSystem). The StateReceiverSystem is explicitly in charge of capturing incoming network updates from the Vert.x EventBus and applying those external coordinate changes to the local entities before the next render cycle.
WorldImpl instance of each client. This architecture strictly reflects the session-based arcade nature of the game, which deliberately lacks user accounts, persistent leaderboards, or save files.MovementSystem will predictively extrapolate entity positions. However, incoming network messages will forcefully correct and overwrite these local predictions with the authoritative coordinates, ensuring eventual consistency without the overhead of distributed locks.World instance) is actively replicated across all connected nodes rather than federated. The Host serves as the definitive source of truth, replicating the state of dynamic entities (such as barrels) and its own avatar to both the Guest and Spectators. Simultaneously, the Guest shares its local avatar’s coordinates, which are replicated back to the Host. Spectators maintain a read-only replicated state, ensuring their local simulation mirrors the active game without interfering with it.LobbyVerticle implements a dedicated timeout and retry mechanism specifically tailored for the Guest connection. If the Guest’s WebSocket closes unexpectedly while the game is active, the server does not immediately terminate the match. Instead, it pauses active event broadcasting for that node and initiates a 30-second timer (guestReconnectTimerId). If the Guest successfully reconnects within this 30-second window, the server transmits a specific payload (isReconnect: true), and the Host replies with a RESTORE_STATE message containing the exact coordinates and lives count of the Guest just before the drop, seamlessly resuming the game. If the timeout expires without a successful reconnection, the server declares a GUEST_TIMEOUT and resolves the match in favor of the Host.GAME_OVER message (with the reason HOST_DISCONNECTED) to the Guest and Spectators, and flushes the lobby state, reopening port connections for a brand-new match.spectators array. This failure is completely transparent to the active players and does not impact the game loop or the server’s stability.RenderingSystem. Visual resources are loaded and sliced into sprite sheet frames only once upon initialization and stored in local memory (assetCache and sourceImageCache). This prevents continuous disk I/O operations and memory reallocation during the intensive rendering loop, ensuring the client remains highly responsive and visually available.LobbyVerticle worker nodes.LobbyVerticle through connection routing and temporal ordering. Access rights are rigidly determined by the URI path utilized during the initial handshake:
/play endpoint. This role is granted the highest authorization, including the rights to dictate global game state, spawn barrels, and trigger game-over conditions./play endpoint. This role is strictly authorized to broadcast updates regarding its own specific entity (the Guest player avatar) and cannot manipulate the environment. Any attempt by a Guest to spawn a barrel would be ignored by the server’s routing logic./spectate endpoint. This role is granted strictly read-only access. The server pushes updates to these clients but does not listen to or process any state-update messages originating from them.ws:// rather than wss://). This is a deliberate architectural trade-off: the game processes no sensitive personal data (PII), financial records, or credentials. Consequently, prioritizing raw throughput and minimal latency over encryption overhead is the optimal choice for a local, real-time multiplayer application.This chapter details the specific technology-dependent choices made to realize the architectural design, focusing on network protocols, data serialization, and the frameworks exploited.
{"type": "HOST_UPDATE", "playerX": 10.5, "lives": 3}). While binary formats like Protocol Buffers (gRPC) would offer smaller payload sizes and faster serialization, JSON was selected for its native integration with the networking framework, its human-readability which greatly sped up debugging, and its sufficient performance over a local network./play vs /spectate) and the chronological order of connections.The project relies on a specific technology stack to achieve its concurrency and rendering goals:
record classes are extensively exploited to define immutable ECS Components (e.g., PositionComponent, VelocityComponent), ensuring thread safety and reducing boilerplate code.LobbyVerticle acts as the HTTP/WebSocket server.ClientVerticle manages the client-side WebSocket.StateReceiverSystem consumes them asynchronously, preventing network operations from blocking the main game loop.Canvas and a GraphicsContext to manually draw and clear sprite sheets frame-by-frame. The core game loop is driven by a JavaFX AnimationTimer, which fires the ECS World.update(deltaTime) method to process physics and render the graphics concurrently at a targeted 60 FPS.Component) from execution logic (GameSystem), making the implementation of network state synchronization straightforward and predictable.To ensure the reliability, correctness, and performance of the distributed game, the system was subjected to a rigorous testing phase, divided into automated testing for core logic and manual acceptance testing for gameplay feel and GUI responsiveness.
Automated testing was implemented using JUnit 5 and managed via the Gradle build tool. The tests are executed automatically within a Continuous Integration (CI) pipeline using GitHub Actions (ci.yml and pr-checks.yml), ensuring that every pull request is validated before being merged. Tests can be run locally by executing ./gradlew test via the command line interface.
PhysicsSystemTest, GravitySystemTest, and MovementSystemTest initialize a mock World, inject entities with specific Component sets, manually invoke the system’s update(deltaTime) method, and assert the resulting state. For example, BoundariesSystemTest acts as a corner-case test by placing an entity outside the screen coordinates and asserting that the system clamps its position back within the allowed arena.LobbyVerticleTest and ClientVerticleTest utilize VertxTestContext to deploy the server and client verticles in a sandboxed, asynchronous test environment. These tests simulate the connection of multiple clients to verify the automatic role assignment (e.g., ensuring the first connection becomes the Host and the second becomes the Guest). Furthermore, EndGameScenariosTest and WinSystemTest simulate the integration between the collision logic and the network broadcast, asserting that terminal states trigger the correct game-over messages.Manual testing was a critical phase of the validation process, conducted in a production-like local area network (LAN) environment with multiple physical machines.
This section outlines the process required to deploy and run “Donkey Kong: Rush” on a local machine. The deployment strategy focuses on simplicity, leveraging the Gradle build automation tool to manage dependencies and execution without requiring complex manual configurations.
Before deploying the software, the target machine must meet the following software requirements:
JAVA_HOME environment variable must be correctly configured and pointing to the JDK 21 installation path.The software does not require a traditional installer wizard. It is deployed and executed directly from the source code using the included Gradle Wrapper.
git clone <repository_url>
cd donkey-kong-feat-multiplayer
./gradlew run
gradlew.bat run
Upon executing the run command, the Gradle Wrapper will automatically download all required third-party dependencies (e.g., Eclipse Vert.x, JavaFX modules, Netty), compile the ECS engine and network logic, and finally launch the DonkeyKongRushUI JavaFX application. The user will be presented with the Main Menu graphical interface, confirming a successful deployment.
The system is designed to be “plug-and-play” and does not rely on external configuration files (e.g., .env, .yaml, or .properties files). All network configurations, such as the target IP address for the Lobby Server, are intentionally exposed directly through the graphical user interface. This choice maximizes user accessibility, allowing players to establish connections dynamically without modifying text files before deployment.
While modern distributed systems frequently rely on containerization tools like Docker or Kubernetes, “Donkey Kong: Rush” explicitly avoids this deployment model for the following reasons:
This section provides a step-by-step guide on how to interact with the “Donkey Kong: Rush” application.
Upon executing the application via Gradle (./gradlew run), the user is greeted by the Main Menu window. This graphical interface provides text fields and buttons to establish network connections.
1. Hosting a Game (Player 1)
LobbyVerticle server and connect your client as the Host.2. Joining a Game (Player 2)
192.168.1.55), and click the “Play” button.3. Spectating a Game

Once the game has started, active players (Host and Guest) can interact with the environment using the following keyboard bindings:
Left Arrow / A to move left, Right Arrow / D to move right.Up Arrow / W to climb up ladders, Down Arrow / S to climb down.Spacebar to jump over obstacles and gaps.Game Objective: Navigate your avatar from the bottom of the screen to the top to rescue Pauline. Avoid the rolling barrels spawned by Donkey Kong. If you collide with a barrel, you will lose a life and respawn at the bottom. The game ends when a player reaches Pauline (Win) or loses all 3 lives (Lose), triggering the Game Over screen.

While the current implementation successfully fulfills the core requirements of a distributed, real-time multiplayer platformer, several avenues for improvement and architectural expansion remain.
Currently, the Guest and Spectator clients act as “dumb terminals” for external entities, strictly overwriting their local entity coordinates with the incoming network snapshots from the Host. On a high-latency network, this could lead to visual stuttering or “rubber-banding.” A primary future improvement would be implementing Entity Interpolation (smoothing the visual transition between the last known network state and the current one) and Client-Side Prediction (allowing the Guest to predict the physics of barrels locally, correcting them only if the server’s authoritative state deviates significantly).
The system relies on users manually sharing LAN IP addresses and hosting the LobbyVerticle on their personal machines. To scale this into a production-grade application, the LobbyVerticle could be decoupled and deployed as a standalone microservice on a cloud provider (e.g., AWS or Google Cloud) using Docker and Kubernetes. Furthermore, introducing a Matchmaking Service would eliminate the need for manual IP entry, automatically pairing available clients into isolated game instances dynamically orchestrated by the cloud provider.
Currently, the system uses unencrypted WebSockets (ws://) and implicitly trusts connections based on routing order. A critical future extension would involve upgrading the infrastructure to utilize WebSocket Secure (wss://) by provisioning TLS certificates. Additionally, implementing a lightweight authentication system using JSON Web Tokens (JWT) would prevent malicious users from spoofing the /play endpoint on public networks, ensuring that only authenticated users can claim the Host or Guest roles.
From a gameplay perspective, the game currently features a single, hardcoded map generated upon startup via the MapFactory. Future iterations could introduce dynamic map loading (e.g., parsing JSON or Tiled map formats). This would require extending the network protocol to broadcast a LEVEL_LOAD message, ensuring that all distributed clients load and synchronize the same level assets before the GAME_START event is triggered.