About arc42
arc42, the template for documentation of software and system architecture.
Template Version 8.2 EN. (based upon AsciiDoc version), January 2023
Created, maintained and © by Dr. Peter Hruschka, Dr. Gernot Starke and contributors. See https://arc42.org.
1. Introduction and Goals
1.1. Requirements Overview
The YOVI_EN1B project is a multi-service platform for playing the abstract board game Game Y (also known as the Y connection game), developed as part of the Software Architecture course at the University of Oviedo. It serves as a practical benchmark for the construction of scalable, maintainable, and well-tested distributed systems, with a specific focus on turn-based game logic and polyglot service integration.
Game Y is played on a triangular board composed of hexagonal cells. Each cell is uniquely identified by barycentric coordinates (x, y, z) where x + y + z = board_size − 1. Two players — Blue (B) and Red ® — take turns claiming cells. A player wins when their occupied cells form a single connected chain that touches all three sides of the triangle simultaneously. The board size is configurable (e.g. 5×5, 7×7, 11×11), and optional game variants such as Explosions (bomb cells) and Double Turn (coming soon) are available for boards of size 7 and above.
The following context diagram illustrates the primary boundaries of the system and its interactions with external actors and infrastructure:
The functional scope encompasses several critical architectural domains:
-
User Identity Management: A centralized service built with Node.js and Express, responsible for registration, authentication (JWT + bcrypt), session management, and persistent user statistics. It is the single gateway to MongoDB and owns all persistence concerns.
-
Hexagonal Game Logic Engine: A high-performance application core implemented in Rust, governing spatial operations on a triangular board of hexagonal cells. This includes move validation, win condition detection via a Union-Find algorithm over barycentric coordinates, bot AI strategy dispatch, and YEN (Y Exchange Notation) game state serialization.
-
AI Opponent System: A pluggable bot registry offering multiple difficulty levels: a Random bot for basic play, a Defensive bot that mirrors opponent threats, a Hard bot powered by Monte Carlo Tree Search (MCTS), and an optional GenerativeAI bot driven by the Google Gemini API. All bots implement a common
YBottrait, making them interchangeable at runtime. -
Game Variants: Optional rule modifiers that extend standard gameplay. The Explosions variant places a hidden bomb cell on the board; landing on it clears all neighbouring pieces via chain-detonation. The Double Turn variant lets each player place two pieces per round. Both require a minimum board size of 7.
-
State Persistence and Resilience: Integration with MongoDB to record all game sessions, move histories, and player statistics. The persistence model ensures that completed games can be replayed move-by-move and that user records survive service restarts.
-
Third-party Bot API: The Gamey engine exposes a documented public HTTP API (
POST /compute,POST /play) that allows external automated agents to participate in the game, fulfilling the course constraint for open extensibility.
1.2. Quality Goals
The architecture of YOVI_EN1B is governed by a technical quality tree rooted in the ISO 25010 standard. The following image shows the ISO 25010 quality model used as a reference:
These high-priority requirements ensure the technical integrity and operational success of the platform:
| Priority | Goal | Architectural Attribute | Operational Scenario |
|---|---|---|---|
1 |
Maintainability |
Modularity |
The system is decomposed into three independent services (webapp, users, gamey) communicating only via HTTP/JSON. Replacing or upgrading one service — for example, swapping the Rust engine for a new version — requires no changes to the other services. SonarCloud quality gate enforces ≥80% coverage on new code and 0 critical issues. |
2 |
Reliability |
Fault Tolerance |
All game sessions, moves, and user statistics are persisted in MongoDB by the users service. In the event of a service restart, the full game history is recoverable from the database; active sessions are not lost. Full environment is reproducible with a single |
3 |
Performance |
Efficiency |
The Gamey Rust engine executes move validation and win detection in under two milliseconds, even on large boards. Bot move response ( |
4 |
Security |
Data Integrity |
All protected API endpoints require a valid JWT Bearer token issued at login (24-hour expiry). Passwords are never stored in plain text — they are hashed with bcrypt (salt rounds 10). All protected endpoints return 401 within 50ms when no token is provided. |
5 |
Usability |
Accessibility |
The React frontend renders the triangular hexagonal board with clear visual feedback of the current game state. WCAG 2.0 AAA compliance verified via Web Accessibility Checker with 0 known issues. A replay feature lets players review past games move by move. |
1.3. Stakeholders
The following individuals have a significant interest in the architectural success and technical implementation of the YOVI_EN1B system:
| Role or Name | Contact | Expectations |
|---|---|---|
Jose Emilio Labra Gayo |
University of Oviedo, Course Professor |
Evaluates architectural decisions, documentation quality, and adherence to arc42 and ADR standards. |
Pablo Gonzalez |
University of Oviedo, Course Instructor |
Reviews technical implementation, testing coverage, and CI/CD pipeline. |
Diego Martín Fernández |
University of Oviedo, Course Instructor |
Assesses system deployment, scalability decisions, and Docker containerization. |
Celia Melendi Lavandera |
University of Oviedo, Course Instructor |
Reviews quality requirements, observability, and monitoring implementation. |
bilalyazicioglu |
University of Oviedo, Developer |
Implements and maintains the Gamey Rust engine: game logic, bot AI, YEN notation, and the public bot API. |
UO300896 |
University of Oviedo, Developer |
Designs and maintains the MongoDB schema, data persistence strategy, and database initialization. |
Th0be |
University of Oviedo, Developer |
Builds and maintains the React/TypeScript frontend: UI components, board rendering, and end-to-end user flows. |
nacho50900 |
University of Oviedo, Developer |
Develops and maintains the Node.js/Express users service: authentication, game orchestration, and REST API. |
2. Architecture Constraints
2.1. Technical Constraints
| Constraint ID | Constraint | Impact | Rationale |
|---|---|---|---|
TC-01 |
Mandatory Tech Stack |
Web application must use TypeScript; core game logic (move validation/generation) must be in Rust. |
Requirement defined by the ASW course to practice polyglot development and language interoperability. |
TC-02 |
Communication Protocol |
Subsystems must communicate via JSON messages. |
Standardizes data exchange between the TypeScript frontend and the Rust backend service. |
TC-03 |
Game Representation (YEN) |
All game states and moves must be represented using the YEN notation. |
Ensures consistency in how the Game Y board is serialized and stored across the system. |
TC-04 |
Architecture Decomposition |
System must be split into at least two subsystems: Web Application and Rust-based web service. |
Encourages modularity and demonstrates the ability to integrate different architectural components. |
TC-05 |
External API |
The system must expose a documented API for third-party bots. |
Facilitates extensibility and allows the game to be played by automated agents. |
TC-06 |
Public Deployment |
The application must be accessible via a public URL. |
Demonstrates the ability to manage cloud deployment and production environments. |
2.2. Organizational Constraints
| Constraint ID | Constraint | Impact | Rationale |
|---|---|---|---|
OC-01 |
Version Control |
All source code and documentation must be hosted in the assigned GitHub repository (Arquisoft/yovi_en1b). |
Enables collaborative development and allows instructors to track progress and contributions. |
OC-02 |
Automated CI/CD |
Deployment and testing must be automated using CI/CD pipelines (e.g., GitHub Actions). |
Ensures software quality and rapid feedback loops throughout the development lifecycle. |
OC-03 |
Documentation Standards |
Use of the arc42 template and ADRs (Architectural Decision Records) is mandatory. |
Provides a standardized structure for architectural documentation and history of key decisions. |
2.3. Conventions
| Constraint ID | Constraint | Impact | Rationale |
|---|---|---|---|
C-01 |
Testing Requirements |
Mandatory inclusion of Unit, Integration, E2E, and Load tests. |
Critical for verifying system reliability and performance under stress. |
C-02 |
AI Strategy Options |
The AI must offer at least two different strategies or difficulty levels. |
Enhances user experience and demonstrates implementation of different algorithmic approaches. |
C-03 |
Language |
All documentation and code comments should be in English. |
Standard practice in software engineering. |
3. Context and Scope
This section delimits the GameY system from all its external communication partners. It defines the boundaries and the interfaces between our system and its environment, both from a business/domain perspective and a technical perspective.
3.1. Business Context
The business context focuses on the external entities (actors) that interact with the GameY system from a functional point of view, ignoring technical details.
Explanation of external domain interfaces:
| Communication Partner | Inputs (to the System) | Outputs (from the System) |
|---|---|---|
User / Player |
Registration credentials, login credentials, game moves, game configuration (board size, strategy). |
Board rendering, game state updates, move results, statistics, match history, leaderboard. |
3.2. Technical Context
This section describes the technical interfaces, channels, and transmission media linking the GameY system to its environment. Based on the docker-compose configuration, the project is divided into a frontend application (webapp), a backend for user handling (users), and a game engine (gamey).
Mapping Input/Output to Channels:
| Communication Partner | I/O | Channel / Protocol |
|---|---|---|
User (Client Device) |
UI interactions, form submissions, game moves, board configuration. |
HTTP for fetching the SPA from the webapp service (Port 80). Once loaded, the browser communicates via HTTP REST with the users service API (Port 3000) for all operations — authentication, game management, statistics, and history. The users service internally delegates game logic to the gamey engine (Port 4000); the frontend does not call gamey directly. |
4. Solution Strategy
The GameY project follows a layered architecture divided into UI, game logic, and backend user handling. Each layer is independently deployable and communicates exclusively via HTTP/JSON.
4.1. Technology decisions
-
The UI is built with React, Vite, and TypeScript; React was chosen due to team familiarity, Vite and TypeScript are course requirements.
-
The game logic is implemented in Rust (course requirement), providing high performance for move validation, win detection, and bot AI.
-
User data and game history are persisted in MongoDB, whose document-oriented model fits naturally with the nested, variable-length structure of game states and move histories. It also integrates well with the Node.js ecosystem via Mongoose. See ADR for full rationale.
-
GitHub is used for version control and CI/CD (course requirement).
-
Docker Compose is used for local and production deployment, orchestrating all four containers with a single command (course requirement).
-
Microsoft Azure (Azure VM) hosts the production environment, chosen for its "Azure for Students" credits and native GitHub Actions integration. See ADR for full rationale.
4.2. Quality goals
-
Maintainability is achieved by decomposing the system into three independent services (webapp, users, gamey) that communicate only via HTTP/JSON contracts, allowing each to be updated independently.
-
Reliability is ensured by the MongoDB persistence layer — all game sessions and moves are persisted immediately, so the system can recover from service restarts without data loss.
-
Performance is handled by the Rust engine, which runs move validation and win detection in under 2ms using a Union-Find algorithm with path compression.
-
Security is enforced through bcrypt password hashing, JWT authentication with 24-hour expiry, and input validation on all endpoints.
-
Usability is achieved through a clear React UI with immediate visual feedback, a replay feature for past games, and accessible design.
5. Building Block View
This section describes the decomposition of the GameY system into its principal building blocks, showing their internal structure and relationships. The system is detailed across two levels: Level 1 shows the overall system components, and Level 2 zooms into the internal structure of each specific component.
5.1. Whitebox Overall System (Level 1)
Here we describe the decomposition of the overall GameY system.
Overview Diagram
Motivation
The system is decomposed into three main building blocks to separate concerns: * The user interface (frontend) is isolated to provide a responsive SPA (Single Page Application). * The user handling is separated into its own microservice to independently manage registrations and user logic. * The game logic is encapsulated in a high-performance Rust engine, ensuring fast execution for bot AI and game state validation.
Contained Building Blocks
| Name | Responsibility |
|---|---|
webapp |
Frontend single-page application. Renders the game board, handles user registration and login, displays statistics, match history, leaderboard, and replay. Communicates exclusively with the users service via HTTP REST. |
users |
Backend REST API service. Handles user registration, authentication (JWT + bcrypt), game creation and management, move orchestration, statistics tracking, leaderboard, and game history. Acts as the single gateway to MongoDB and delegates game logic to the gamey engine. |
gamey |
Core game engine and bot service written in Rust. Responsible for move computation, win condition detection (Union-Find), bot AI strategy dispatch (random, defensive, MCTS, Gemini), and YEN notation serialization. Stateless; no database connection. |
5.2. Level 2
This level zooms into the internal structure of the three main building blocks defined in Level 1.
5.2.1. Whitebox: webapp
Overview Diagram
Contained Building Blocks
| Name | Responsibility |
|---|---|
App.tsx |
Root React component; sets up routing and global state. |
Game board components |
Renders the triangular hexagonal board using YEN state, handles cell selection and move submission. |
Auth components |
Registration and login forms; stores JWT token for authenticated requests. |
History and replay components |
Displays past games and drives the step-by-step move replay using |
Leaderboard component |
Fetches and displays the ranked player list from |
5.2.2. Whitebox: users
Overview Diagram
Contained Building Blocks
| Name | Responsibility |
|---|---|
users-service.js |
Main Express server entry point. Mounts all route modules and connects to MongoDB via Mongoose. |
authRoutes |
Handles |
userRoutes |
Handles |
gameRoutes |
Handles game lifecycle: |
playRoute |
Public |
leaderBoardRoute |
|
MongoUserRepository |
Data access layer implementing the repository pattern. Encapsulates all MongoDB queries for users, games, and statistics. |
5.2.3. Whitebox: gamey
Overview Diagram
Contained Building Blocks
| Name | Responsibility |
|---|---|
main.rs / lib.rs |
Entry point and library exports for the Rust application. It initializes the engine (e.g., HTTP server mode on Port 4000). |
web |
Manages the web interface components and server routing. |
core |
Contains the core game logic, including actions, coordinates, game state, and player management. |
bot |
Implements the bot registry and automated player logic. |
notation |
Provides parsers and support for game notation formats (YEN, YGN). |
6. Runtime View
Here are the main runtime scenarios that demonstrate how YOVI’s components interact during gameplay.
The system consists of three components:
-
Frontend (FE) — React + Vite, runs on port 80
-
Users Service (USERS) — Node.js + Express + MongoDB, runs on port 3000
-
Gamey Logic (GAMEY) — Rust game engine, runs on port 4000
6.1. Scenario 1: User Registration and Login
Notable aspects:
-
Passwords are never stored in plain text — bcrypt with salt rounds 10
-
JWT token expires after 24 hours and must be sent as
Authorization: Bearer <token>on all protected requests -
Username uniqueness is enforced at DB level (unique index)
6.2. Scenario 2: Player vs Player Game
Notable aspects:
-
Starting turn (B or R) is randomly assigned using
crypto.randomInt— cryptographically secure -
Player field in each move is inferred from
current_turn— frontend does not send it -
yen_stateper move is computed by Gamey’s/computeendpoint, not by the frontend or backend -
Gamey returns a
winnerfield after every/computecall — see Scenario 4 for win detection details -
DRAW result (user quit) does not update statistics
6.3. Scenario 3: Player vs Bot Game
Notable aspects:
-
The backend acts as a proxy between the frontend and Gamey — the frontend never calls Gamey directly
-
POST /compute— Gamey receives{ yen_state_prev, coordinates }and returns{ yen_state, winner } -
POST /play— Gamey receives{ yen_state, strategy, difficulty_level, board_size }and returns{ coordinates, yen_state, winner } -
Both endpoints return a
winnerfield — see Scenario 4 for win detection details -
Bot move is saved automatically in the DB by the Users Service — frontend does not need to call
POST /games/:id/movefor the bot -
If it is the first move,
yen_state_prev/yen_stateis sent asnulland Gamey generates the initial board state fromboard_size
6.4. Scenario 4: Win Detection
Notable aspects:
-
Win detection is fully handled by Gamey — the Users Service only reacts to the
winnerfield -
The backend calls
autoFinishIfWinnerafter every move (both player and bot) — ifwinneris not null, the game is finished and statistics updated automatically -
winner: "B"→ human player wins → result isWIN;winner: "R"→ bot wins → result isLOSS -
Duration is calculated automatically from
game.created_atwhen auto-finishing
6.5. Scenario 5: Game Replay
Notable aspects:
-
Each move stores its resulting
yen_state, so the frontend can reconstruct the board at any point in time -
History endpoint excludes the moves array for a lighter response — full moves are fetched separately via
GET /games/:id/moves
7. Deployment View
7.1. Infrastructure Level 1
- Motivation
-
Game Y uses Docker Compose to orchestrate four independent containers: the
webapp(React frontend), theusersservice (Node.js/Express user management API), thegameyservice (Rust game engine), and amongodbcontainer for persistent storage. This separation ensures that each service can be developed, built, and scaled independently. MongoDB is the chosen database because its document-oriented model fits naturally with the variable structure of game states and move histories, and it integrates well with the Node.js ecosystem.
Only the users service connects to MongoDB. This is a deliberate architectural decision: gamey manages active game state in memory during a match (which is sufficient for its role as a pure game engine), and once a game ends the result is sent to the users service, which is responsible for all persistence — player accounts, statistics, and game history. This avoids two services competing to write to the same database and keeps data ownership clear.
- Quality and/or Performance Features
-
-
Isolation: Each service runs in its own Docker container, preventing dependency conflicts and simplifying upgrades.
-
Portability:
docker-compose up --buildreproduces the full environment on any machine with Docker installed. -
Independent scaling: The
gameyandusersservices can be scaled individually based on load without affecting the frontend. -
Clear interface boundaries: Services communicate only via HTTP REST, making the contracts explicit and testable.
-
Development efficiency: Docker Compose orchestrates the full local setup with a single command; no manual service wiring is required.
-
- Mapping of Building Blocks to Infrastructure
| Building Block | Container | Technology | Exposed Port | Connects to |
|---|---|---|---|---|
User Interface |
Webapp Container |
React + TypeScript + Vite |
80 |
Users (3000) |
User Management & Persistence |
Users Container |
Node.js + Express |
3000 |
MongoDB (27017), Gamey (4000) |
Game Logic & Bot |
Gamey Container |
Rust (Cargo) |
4000 |
— |
Persistent Storage |
MongoDB Container |
MongoDB 7 |
27017 (internal) |
— |
7.1.1. Container Descriptions
Webapp Container — Serves the React single-page application to the browser. It is the only container directly accessible from outside the Docker network. It communicates exclusively with the users service for all operations: authentication, game creation, move submission, statistics, history, and leaderboard. It never calls the gamey engine directly.
Users Container — Manages player accounts, authentication, statistics, and game history. It is the only service that connects to MongoDB, acting as the single gateway for all persistent data. When a game finishes, the result is reported here so it can be stored and reflected in player stats.
Gamey Container — A pure game engine written in Rust. It handles game creation, move validation, win condition checking, and bot moves entirely in memory. It has no database connection; active game state lives in memory for the duration of a match. Once the game ends, the result is communicated back to the frontend, which then reports it to the users service.
MongoDB Container — Provides persistent NoSQL storage exclusively for the users service. It runs as a Docker container within the same Compose network, so it is not directly accessible from outside.
7.2. Infrastructure Level 2
7.2.1. Gamey Container
The Gamey container runs the Rust application that implements the full game logic for Game Y. Its internal components are:
-
Web Interface: Exposes HTTP endpoints on port 4000 that the React frontend calls to create games, submit moves, and query game state.
-
Game Engine (core): Contains the core data model — coordinates, board state, player management, and action dispatch. This is the authoritative source for game rules. Win condition checking is performed internally within the engine after every move, using a graph traversal (DFS/BFS) over the hexagonal grid to detect whether a player’s connected chain touches all three sides of the triangular board (see section 6 for the runtime scenario).
-
Bot Registry: Maintains a set of registered bot strategies. When a player chooses to play against the computer, the bot registry selects the appropriate bot and returns its move to the game engine.
-
Notation Parser: Supports YEN (Yovi Extended Notation) and YGN (Yovi Game Notation) for encoding and decoding move sequences. This enables game export, replay, and interoperability.
7.2.2. Users Container
The Users container is a Node.js/Express service that acts as the main backend for the platform. It handles user registration, authentication (JWT + bcrypt), game lifecycle management, statistics tracking, leaderboard, and the public bot API. It is the only service that connects to MongoDB (via Mongoose) and the only service that calls the Gamey engine internally — the frontend never reaches Gamey directly. Routes are split into independent modules: authRoutes, userRoutes, gameRoutes, playRoute, and leaderBoardRoute, all backed by a MongoUserRepository implementing the repository pattern.
7.2.3. Webapp Container
The Webapp container serves the React single-page application compiled by Vite. It is served via nginx on port 80, configured with try_files $uri /index.html to support client-side routing without 404s on page reload. All interactions are handled client-side, with REST calls made exclusively to the users service (port 3000). There is no server-side rendering and no direct connection to the gamey engine.
7.2.4. MongoDB Container
The MongoDB container is exclusively used by the users service. It holds two collections:
-
players: User accounts, hashed credentials, and per-player statistics (wins, losses, games played).
-
game_history: Archived completed games including the full sequence of moves, the result, and the participants. This allows the frontend to display match history and enables future replay functionality.
gamey does not connect to MongoDB. Active game state is kept in memory within the Rust process for the duration of a match, which is sufficient for its role as a game engine. When a game concludes, the frontend reports the result to the users service, which writes it to game_history.
The MongoDB port (27017) is only exposed internally within the Docker Compose network and is not reachable from outside the host machine.
The docker-compose entry for this container is:
mongodb:
image: mongo:7
volumes:
- mongo_data:/data/db
The named volume mongo_data ensures that data persists across container restarts. Note that the port is intentionally not published to the host — only the users container needs to reach it, and it can do so through the internal Docker network.
8. Cross-cutting Concepts
8.1. Domain Model: Barycentric Coordinate System
The Game Y board is a triangle subdivided into hexagonal cells. Every cell is uniquely addressed by a triple of non-negative integers (x, y, z) — barycentric coordinates — that satisfy the invariant:
x + y + z = board_size − 1
Each coordinate component encodes the cell’s distance from one of the three sides of the triangle:
-
x = 0 → cell touches side A (bottom edge)
-
y = 0 → cell touches side B (left edge)
-
z = 0 → cell touches side C (right edge)
Corner cells touch exactly two sides (e.g. (0, 0, n) touches both A and B); interior cells touch none. A board of size N contains exactly N(N+1)/2 cells in total.
Adjacency is defined as any coordinate reachable by incrementing one component by 1 and decrementing another by 1, keeping the sum constant. Interior cells have six neighbours; edge cells have four; corner cells have two. This model is implemented in gamey/src/core/coord.rs and is the authoritative coordinate representation used throughout the entire system.
8.2. Game State Serialization: YEN (Y Exchange Notation)
All game states exchanged between the Users Service and the Gamey engine are encoded as YEN (Y Exchange Notation) — a compact, JSON-serializable format inspired by FEN (Forsyth-Edwards Notation) used in chess. YEN is the single wire format for game state across the entire system (constraint TC-03).
A YEN document contains four mandatory fields and two optional ones:
| Field | Type | Description |
|---|---|---|
|
integer |
Side length of the triangular board (e.g. 5 for a 5×5 board with 15 cells). |
|
integer (0 or 1) |
Index of the player whose turn it is next. |
|
array of chars |
Symbol for each player, e.g. |
|
string |
Compact board encoding: rows separated by |
|
array of strings (optional) |
Active game variant names, e.g. |
|
string (optional) |
Comma-separated flat cell indices of bomb positions, e.g. |
Example YEN for a board of size 3 where Blue has claimed the top vertex:
{
"size": 3,
"turn": 1,
"players": ["B", "R"],
"layout": "B/../.."
}
The layout string encodes rows from top to bottom: row 0 has 1 cell, row 1 has 2, row r has r+1. An empty board of size 3 is "./../.." (or equivalently "…"`with the `/ separators omitted for a flat representation; the Gamey parser handles both). YEN is generated and consumed exclusively by gamey/src/notation/yen.rs.
8.3. Win Detection: Union-Find with Side-Touching Flags
Win detection is a cross-cutting concern executed by the Gamey engine after every move and returned to the Users Service as a winner field. A player wins when their occupied cells form a single connected component that simultaneously touches all three sides of the triangular board.
The Gamey engine implements this with an incremental Union-Find (Disjoint Set Union) data structure augmented with three boolean flags per set root: touches_side_a, touches_side_b, and touches_side_c.
The algorithm works as follows:
-
When a cell is placed, a new singleton set is created for it with the side-touching flags derived from its coordinates.
-
The cell’s neighbors already owned by the same player are merged into the new set using
union()with path compression. -
During
union(), the merged root inherits the bitwise OR of both roots' side-touching flags. -
If the merged root has all three flags set (
touches_side_a ∧ touches_side_b ∧ touches_side_c),place_piece()returnstrue— the placing player wins immediately.
This approach runs in near-constant time per move (amortized O(α(n)) with path compression), replacing the need for a full DFS/BFS traversal after each move. The implementation lives in gamey/src/core/board.rs.
Win condition check per move:
1. create set S for new cell C with flags {a=touches_A?, b=touches_B?, c=touches_C?}
2. for each neighbor N of C owned by same player:
root_S ← find(S), root_N ← find(N)
merge(root_S, root_N): root.flags |= merged.flags
3. if root.touches_a AND root.touches_b AND root.touches_c → winner!
After a bomb detonation (Explosions variant), the union-find structure is fully rebuilt from the surviving pieces to prevent stale side-touching flags from leaked orphaned sets causing phantom wins.
8.4. Security Concepts
Security concerns are handled uniformly across all endpoints that require an authenticated user. The following mechanisms are in place:
Password Storage: Passwords are never stored in plain text. When a user registers, the password is hashed using bcrypt with 10 salt rounds before it is written to MongoDB. On login, bcrypt.compare() is used to verify the supplied password against the stored hash. The plain-text password is never logged or retained.
Session Authentication (JWT): Upon successful login, the Users Service signs a JSON Web Token (JWT) with a 24-hour expiry. The token is returned to the client and must be included as a Authorization: Bearer <token> header on all protected API calls. The server validates the signature and expiry on every request before processing it.
Username Uniqueness: A unique index on the username field in MongoDB enforces that no two accounts can share the same username, even under concurrent registration requests.
Input Validation: All incoming JSON payloads are validated for required fields and correct types before any business logic is executed. Invalid requests are rejected with a 400 response before they reach the database or the game engine.
The Gamey engine itself does not perform authentication — it is an internal service reachable only within the Docker Compose network, not directly exposed to the public internet.
8.5. Inter-service Communication
All three services communicate exclusively via synchronous HTTP REST with JSON payloads (constraint TC-02). No message broker, shared memory, or direct database access across services is used.
| Direction | Protocol | Purpose |
|---|---|---|
Browser → webapp |
HTTPS/HTTP (port 80) |
Serves the React SPA to the client. |
webapp → users |
HTTP REST (port 3000) |
User registration, login, game creation, move submission, history retrieval. |
webapp → gamey |
HTTP REST (port 4000) |
Not used directly by the frontend in production. Gamey is called internally by the users service. |
users → gamey |
HTTP REST (port 4000) |
|
External bots → gamey |
HTTP REST (port 4000) |
Third-party automated agents may call |
users → MongoDB |
MongoDB Wire Protocol (port 27017) |
All persistence operations — user accounts, game records, move histories. |
The users service acts as an orchestrator: it receives a player move from the frontend, delegates the state computation to Gamey, persists the result in MongoDB, and returns the updated game to the frontend. The frontend never calls Gamey directly during a live game session.
8.6. Persistence Model
Persistent state is owned exclusively by the Users Service and stored in MongoDB. MongoDB’s document model was chosen because game states and move histories have a variable, nested structure that maps naturally to JSON documents (see deployment decision in Section 4).
The database contains two collections:
players collection: Each document represents a registered user and stores the username, the bcrypt-hashed password, and aggregated statistics (wins, losses, games played). A unique index on username ensures account integrity.
games collection: Each document represents a single game session. It stores the participants, the board size, the game type (PLAYER or BOT), the current status (IN_PROGRESS or FINISHED), the final result (WIN, LOSS, or DRAW), and an embedded array of moves. Each move document records the player who moved, the coordinates chosen, the resulting YEN state, and a sequential move number. This embedded design allows the full game to be retrieved and replayed in a single query.
The gamey engine intentionally has no database connection. Active game state lives in the Rust process memory for the duration of a match. When a game concludes, the result is propagated back through the users service, which writes it to MongoDB. This clear ownership boundary avoids write conflicts and keeps the data model simple.
8.7. Bot AI Strategy Pattern
The bot AI system in Gamey is structured around the Strategy pattern. All bots implement the YBot trait, which exposes a single method: given a board state (as a YEN document), return the chosen coordinates. This interface decouples the bot selection logic from the game engine core and makes it trivial to add new strategies without modifying existing code.
The following bot strategies are registered in the YBotRegistry at startup:
| Strategy | Difficulty | Description |
|---|---|---|
|
Easy |
Selects a uniformly random empty cell. Used as a baseline and for testing. |
|
Medium |
Identifies and blocks the cell adjacent to the opponent’s most recent move, prioritising defence over offence. |
|
Hard |
Implements Monte Carlo Tree Search (MCTS) — simulates many random playouts from the current position to estimate the value of each candidate move and selects the highest-value option. |
|
Variable |
Delegates move selection to the Google Gemini API via |
The strategy and difficulty level are selected by the frontend when creating a bot game and passed to Gamey on each POST /play call. The registry looks up the matching YBot implementation and invokes it, returning the coordinates to the users service.
8.8. Game Variants
Game variants are optional rule modifiers that extend the standard Y game. They are represented as an enum (GameVariant) in the Rust engine and encoded in the variants array of the YEN state. Variant Explosions require a minimum board size of 7.
Explosions (Bomb Mode): At game initialization, one or more bomb cells are randomly placed on the board. Their positions are encoded in the YEN e field as a comma-separated list of flat cell indices. A bomb cell looks empty to players — they can choose it as a move. When a player lands on a bomb cell: (1) the player’s piece is placed normally, (2) the bomb is consumed, and (3) all occupied neighbour cells are cleared back to empty via a BFS chain-detonation (adjacent bombs also detonate). After detonation, the union-find structure is fully rebuilt from surviving pieces to maintain win-detection integrity. Chain detonation means a cluster of adjacent bombs all explode together when the first one is triggered.
8.9. Error Handling
Error handling follows a centralized, typed model in the Rust engine and a consistent HTTP status code convention in the Node.js service.
In Gamey, all possible failure modes are expressed as variants of a single GamyError enum (defined in gamey/src/gamey_error.rs). This enum is used throughout the engine and the HTTP handlers, and maps to appropriate HTTP status codes (e.g. 400 for invalid moves, 404 for unknown games, 500 for internal failures). This avoids stringly-typed errors and ensures all error paths are handled exhaustively.
In the Users Service, errors are caught at the controller layer and returned as JSON responses with a standard shape: { "error": "<message>" }. HTTP status codes follow REST conventions: 201 for resource creation, 200 for retrieval, 400 for validation failures, 401 for authentication errors, 404 for not-found resources, and 500 for unexpected failures.
The frontend displays error messages returned from the API directly to the user where relevant (e.g. "Username already taken" during registration, "Invalid move" during gameplay).
9. Architecture Decisions
All project-specific architecture decisions are collected in one place. You can find them all in the wiki here: link: https://github.com/Arquisoft/yovi_en1b/wiki/Architectural-decisions. This page is the central reference for decisions made in the project.
10. Quality Requirements
10.1. Quality Tree
The following mind map illustrates the primary quality goals for YOVI, breaking them down into concrete quality scenarios (S1 to S8) strictly based on the system’s actual architecture and testing capabilities.
10.2. Quality Scenarios
The table below describes the specific scenarios derived from the quality tree, providing concrete and measurable situations to evaluate the architecture based on the defined goals and the actual test suites present in the repository.
| Ref | Quality Attribute | Scenario Description | Priority |
|---|---|---|---|
S1 |
Maintainability (Modularity) |
Updating the Rust engine or Node API can be done independently without affecting the WebApp, as long as the REST HTTP/JSON contracts remain unchanged. |
High |
S2 |
Reliability (Fault Tolerance) |
If the Users service restarts unexpectedly, active players can resume their games seamlessly since all moves and states are immediately persisted to MongoDB. |
High |
S3 |
Performance (Efficiency) |
The Gamey engine computes move validation and win conditions using Union-Find in under 2ms per request, ensuring rapid turn-based responses. |
High |
S4 |
Performance (Scalability) |
During automated load testing (e.g., using Artillery), the system is capable of sustaining peak loads of up to 15 concurrent complete user lifecycles per second without dropping requests. |
High |
S5 |
Security (Data Integrity) |
Unauthenticated requests to modify game state are blocked (401 Unauthorized), and database dumps cannot reveal passwords because they are salted and hashed via bcrypt (10 rounds). |
High |
S6 |
Usability (Accessibility) |
Players are provided with immediate visual feedback on invalid moves in the React interface, and can use the history replay feature to review past matches step-by-step. |
Medium |
S7 |
Testability (E2E Automation) |
Critical user journeys (register, login, play against bots) are continuously verified in the CI/CD pipeline using Playwright and Cucumber against a full containerized environment. |
High |
S8 |
Testability (State Isolation) |
Automated test suites can clean up their own database footprint after execution using test-specific endpoints (like |
High |
11. Risks and Technical Debts
11.1. Technical Risks
| Priority | Risk | Impact | Mitigation |
|---|---|---|---|
1 — Critical |
Gamey service instability under load The Rust game engine has no persistent state and manages active game sessions in memory. Under concurrent load it has shown connection resets and crashes, requiring a full container restart to recover. |
Total service unavailability for all active games. Users lose their current game session with no recovery path. |
Add a health-check endpoint to Gamey and configure Docker Compose |
2 — High |
Single point of failure: Users service and MongoDB on one VM The entire backend (Users service, MongoDB, Gamey) runs on a single Azure VM (IP 4.233.184.98). No redundancy or failover exists. |
A VM outage, network issue, or resource exhaustion takes down the entire platform simultaneously. |
Document the risk explicitly. For the scope of this course, accept it. For production, migrate to a managed service (e.g., MongoDB Atlas) and deploy behind a load balancer with at least two VM instances. |
3 — High |
JWT secret not rotated; hardcoded fallback in code The JWT signing secret is loaded from an environment variable but falls back to a hardcoded default if unset. A leaked or default secret allows token forgery for any user. |
Full authentication bypass — an attacker could forge tokens and impersonate any user including administrators. |
Enforce the environment variable as mandatory (throw on startup if unset). Rotate the secret and redeploy. Store it in a secrets manager rather than a |
4 — Medium |
No input sanitization on board coordinates The Users service forwards coordinates received from the frontend directly to Gamey without validating that they are integers within the valid triangular board range. |
Malformed input could crash Gamey or cause undefined behaviour in the Rust engine, triggering a full service restart. |
Add a validation middleware in the Users service that rejects moves where |
5 — Medium |
Grafana excessive disk I/O degrading VM performance Grafana has been observed causing high disk I/O on the shared VM, slowing down all co-located services including the Users service and MongoDB. |
Increased response latency across the entire platform, potential timeout cascades under normal load. |
Tune Grafana’s retention policy and reduce scrape frequency in Prometheus. Consider moving monitoring to a separate lightweight instance or disabling Grafana between demos. |
6 — Low |
VITE_API_URL baked at build time The frontend URL for the Users service API is embedded at Docker image build time via a Vite build argument. Changing the VM IP or adding a domain requires a full image rebuild and redeploy. |
Any infrastructure change (IP rotation, domain migration) breaks the frontend without a rebuild. |
Use a runtime configuration file (e.g., |
11.2. Technical Debts
| Priority | Debt | Remediation |
|---|---|---|
1 — Resolved |
Legacy bot strategy keys in MongoDB (Fixed)
Early versions stored bot results under |
Resolved. The migration script was run and the production database was wiped and restarted with |
2 — Medium |
No rate limiting on public endpoints
The public |
Integrate |
3 — Medium |
Game moves array not excluded from history endpoint
|
Already partially addressed: |
12. Glossary
| Term | Definition |
|---|---|
ADR (Architecture Decision Record) |
A short document that captures an important architectural decision, the context that motivated it, and its consequences. Used in this project to track key design choices such as the technology stack and communication protocols. |
arc42 |
A template for documenting software and system architecture. Provides a standardised structure of 12 sections covering goals, constraints, building blocks, runtime behaviour, deployment, and quality requirements. |
ASW (Arquitectura del Software) |
Software Architecture course at the Universidad de Oviedo in which this project (YOVI_EN1B) was developed. |
Authorization Header |
An HTTP request header used to transmit authentication credentials. In this system, it carries the JWT token in the format |
bcrypt |
A password hashing algorithm used in the Users service to store user credentials securely. Passwords are never stored in plain text; bcrypt applies a salt and a configurable cost factor before storing the hash. |
Bot |
An automated player controlled by the Gamey engine. The system supports four bot strategies: |
bot_id |
The public-facing identifier for a bot strategy used in the external API ( |
CI/CD |
Continuous Integration / Continuous Deployment. Automated pipelines (GitHub Actions) that build, test, and deploy the application on every push to the repository. |
coordinates |
A pair |
CORS (Cross-Origin Resource Sharing) |
An HTTP mechanism that controls which origins are permitted to call the Users service API from a browser. Configured in the Users service middleware to allow requests from the frontend origin. |
crypto.randomInt |
A Node.js built-in cryptographically secure random integer generator. Used in the Users service to assign the starting turn (B or R) at the beginning of each game. |
current_turn |
A field stored on each game document in MongoDB indicating whose turn it is ( |
difficulty_level |
A label derived from the bot strategy that describes the challenge level presented to the player: |
Docker Compose |
A tool for defining and running multi-container Docker applications using a single |
DFS (Depth-First Search) |
A graph traversal algorithm used by the Gamey engine to detect win conditions. After each move it traverses the board from a player’s pieces to determine whether they form a connected chain touching all required edges of the triangular board. |
Express |
A minimal Node.js web framework used to build the Users service REST API. Routes are organised into separate modules (authRoutes, userRoutes, gameRoutes, playRoute, leaderBoardRoute). |
Gamey |
The Rust-based game engine service running on port 4000. Responsible for all game logic: move computation, win detection, bot strategy execution, and YEN state management. Has no database connection; all state is in memory during a match. |
game_type |
A field on the game document indicating whether the game is played against another human ( |
GitHub Actions |
The CI/CD platform used to automate testing and deployment of the YOVI_EN1B project on every push to the repository. |
Grafana |
A monitoring and visualisation tool connected to Prometheus. Used to display real-time dashboards of system health and performance metrics for the YOVI platform. |
JWT (JSON Web Token) |
A compact, self-contained token used for authentication. Issued by the Users service on login and sent by the client as a Bearer token on all protected requests. Contains the user ID and expires after 24 hours. |
leaderboard |
A ranked list of players by performance. The Users service exposes |
lean() |
A Mongoose method that returns plain JavaScript objects (POJOs) instead of full Mongoose documents. Used in |
MongoDB |
A document-oriented NoSQL database used for persistent storage of user accounts, authentication credentials, statistics, and game history. Accessed exclusively by the Users service via Mongoose. |
Mongoose |
An ODM (Object Document Mapper) library for MongoDB and Node.js. Provides schema definitions, validation, and query helpers used throughout the Users service. |
mongo-data |
The named Docker volume that persists MongoDB data across container restarts. Removed with |
move |
A single action taken by a player during a game, identified by coordinates |
nock |
A Node.js HTTP interception library used in tests to mock outgoing HTTP calls from the Users service to Gamey, without requiring a real Gamey instance to be running. |
Prometheus |
A time-series monitoring system that scrapes metrics from the Users service. Works alongside Grafana to provide operational visibility into request rates, error rates, and response times. |
repository pattern |
An architectural pattern used in the Users service to abstract database access. |
result |
The outcome of a finished game from the perspective of the human player: |
SonarCloud |
A static analysis service used to enforce code quality standards on the repository. Acts as a quality gate requiring ≥80% coverage on new code and zero critical issues before merging. |
SPA (Single Page Application) |
A web application architecture where the browser loads a single HTML page and dynamically updates content via JavaScript. The YOVI frontend is a React SPA built with Vite and TypeScript, served on port 80. |
strategy |
The internal Gamey identifier for a bot algorithm. Valid values are |
triangular board |
The Game Y playing surface. For board size |
Users service |
The Node.js/Express backend service running on port 3000. Responsible for user registration, authentication, game management, statistics tracking, leaderboard, and the public bot API ( |
VM (Virtual Machine) |
The Azure cloud virtual machine (IP |
winner |
A field returned by Gamey’s |
yen_state |
The serialized board state in YEN (Yovi Extended Notation) format. Stored with each move and passed between the frontend, Users service, and Gamey to reconstruct the board at any point in time. |
YEN (Yovi Extended Notation) |
The notation format used to encode Game Y board states. A layout string where rows are separated by |
YGN (Yovi Game Notation) |
A notation format supported by Gamey for encoding full move sequences, enabling game export and replay functionality. |
