Implements real-time multiplayer mode where players join rooms via 6-character codes and synchronize champion selections, lane toggles, and rerolls across all clients.
Server (Socket.io + Express)
RoomManager: Handles room lifecycle (create/join/leave/auto-delete), isolated state per room, server-side reroll computation
MultiplayerServer: WebSocket event handlers for room operations, broadcasts state updates to all room clients
Fetches champion data on startup to enable server-side random selection
Client (Angular + Signals)
MultiplayerService: WebSocket client, manages connection state and room operations via typed Socket.io events
RandomizerStateService: Extended with multiplayer mode detection, delegates to server when isMultiplayerMode() is true, otherwise uses local state
All state computed properties (assignments, disabled lanes, reroll bank) transparently switch between local and remote sources
// State service automatically uses multiplayer when connected
publicreadonlyassignmentsArray=computed(()=>{if(this.isMultiplayerMode()){constroomState=this.multiplayerService.roomState();returnthis.lanes.map(lane=>roomState?.assignments[lane]||null);}returnArray.from(this.assignments().values());});// Actions become async and delegate to server
publicasyncchangeChampion(lane: Lane):Promise<void>{if(this.isMultiplayerMode()){awaitthis.multiplayerService.rerollLane(lane);return;}// Local reroll logic...
}
UI
Multiplayer dialog (Material tabs) for create/join room flows
Control header shows "Multiplayer" button (single-player) or room code + player count with "Leave Room" button (multiplayer)
If you need me to access, download, or install something from one of these locations, you can either:
Configure Actions setup steps to set up my environment, which run before the firewall is enabled
Add the appropriate URLs or hosts to the custom allowlist in this repository's Copilot coding agent settings (admins only)
Original prompt
Feature Request: Online Multiplayer Functionality
We need to extend the existing app with an online multiplayer mode.
This is not a new app — it’s an additional functionality that will change parts of the implementation.
Requirements
Interface
Keep the current interface mostly identical for now (we’ll adjust later as needed).
Multiplayer System
Add support for multiple players connected online.
Each player can select their lane, and all clients must stay synchronized in real time.
Networking
Use WebSockets for real-time synchronization.
When a player rerolls their lane, the computation should happen on a Node.js server, which then dispatches the updated state to all connected clients.
Rooms
Implement a room system with the following actions:
Create a room
Join a room
Leave a room
Delete a room (automatically when empty or when the owner leaves)
Each room should have its own isolated state and synchronized player data.
Server Responsibilities
Manage all synchronization, state updates, and event broadcasting.
Ensure consistency between clients and handle reconnections gracefully.
Notes
Focus on building a solid architecture for WebSocket-based state synchronization.
Keep UI changes minimal; prioritize backend and communication logic first.
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.
Implements real-time multiplayer mode where players join rooms via 6-character codes and synchronize champion selections, lane toggles, and rerolls across all clients.
## Server (Socket.io + Express)
- **RoomManager**: Handles room lifecycle (create/join/leave/auto-delete), isolated state per room, server-side reroll computation
- **MultiplayerServer**: WebSocket event handlers for room operations, broadcasts state updates to all room clients
- Fetches champion data on startup to enable server-side random selection
## Client (Angular + Signals)
- **MultiplayerService**: WebSocket client, manages connection state and room operations via typed Socket.io events
- **RandomizerStateService**: Extended with multiplayer mode detection, delegates to server when `isMultiplayerMode()` is true, otherwise uses local state
- All state computed properties (assignments, disabled lanes, reroll bank) transparently switch between local and remote sources
```typescript
// State service automatically uses multiplayer when connected
public readonly assignmentsArray = computed(() => {
if (this.isMultiplayerMode()) {
const roomState = this.multiplayerService.roomState();
return this.lanes.map(lane => roomState?.assignments[lane] || null);
}
return Array.from(this.assignments().values());
});
// Actions become async and delegate to server
public async changeChampion(lane: Lane): Promise<void> {
if (this.isMultiplayerMode()) {
await this.multiplayerService.rerollLane(lane);
return;
}
// Local reroll logic...
}
```
## UI
- Multiplayer dialog (Material tabs) for create/join room flows
- Control header shows "Multiplayer" button (single-player) or room code + player count with "Leave Room" button (multiplayer)
- No other interface changes
## Protocol
Client→Server: `createRoom`, `joinRoom`, `leaveRoom`, `toggleLane`, `rerollLane`, `rollAllAssignments`
Server→Client: `roomState` (full sync), `playerJoined`, `playerLeft`, `error`
Room state includes assignments map, disabled lanes set, reroll bank, and player list with optional lane selections.
> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by firewall rules:
>
> - `accounts.google.com`
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=3463 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-21725847 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5709536632044235392,11397553272857069314,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --user-data-dir=/tmp/karma-21725847 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 --noerrdialogs --ozone-platform=headless --ozone-override-screen-size=800,600 --use-angle=swiftshader-webgl http://localhost:9876/?id=21725847` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4156 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-81211339 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,7466260447177359141,14053898345622560324,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - `cdn.merakianalytics.com`
> - Triggering command: `ng build --configuration=development (random-lol)` (dns block)
> - `clients2.google.com`
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=3463 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-21725847 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5709536632044235392,11397553272857069314,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --user-data-dir=/tmp/karma-21725847 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 --noerrdialogs --ozone-platform=headless --ozone-override-screen-size=800,600 --use-angle=swiftshader-webgl http://localhost:9876/?id=21725847` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4156 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-81211339 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,7466260447177359141,14053898345622560324,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - `clientservices.googleapis.com`
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=3463 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-21725847 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5709536632044235392,11397553272857069314,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --user-data-dir=/tmp/karma-21725847 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 --noerrdialogs --ozone-platform=headless --ozone-override-screen-size=800,600 --use-angle=swiftshader-webgl http://localhost:9876/?id=21725847` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4156 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-81211339 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,7466260447177359141,14053898345622560324,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - `example.com`
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=3463 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-21725847 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5709536632044235392,11397553272857069314,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --user-data-dir=/tmp/karma-21725847 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 --noerrdialogs --ozone-platform=headless --ozone-override-screen-size=800,600 --use-angle=swiftshader-webgl http://localhost:9876/?id=21725847` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4156 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-81211339 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,7466260447177359141,14053898345622560324,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - `fonts.googleapis.com`
> - Triggering command: `ng build (random-lol)` (dns block)
> - `redirector.gvt1.com`
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=3463 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-21725847 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5709536632044235392,11397553272857069314,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --user-data-dir=/tmp/karma-21725847 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 --noerrdialogs --ozone-platform=headless --ozone-override-screen-size=800,600 --use-angle=swiftshader-webgl http://localhost:9876/?id=21725847` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4156 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-81211339 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,7466260447177359141,14053898345622560324,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - `safebrowsingohttpgateway.googleapis.com`
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=3463 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-21725847 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5709536632044235392,11397553272857069314,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --user-data-dir=/tmp/karma-21725847 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 --noerrdialogs --ozone-platform=headless --ozone-override-screen-size=800,600 --use-angle=swiftshader-webgl http://localhost:9876/?id=21725847` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4156 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-81211339 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,7466260447177359141,14053898345622560324,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - `www.google.com`
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=3463 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-21725847 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5709536632044235392,11397553272857069314,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --user-data-dir=/tmp/karma-21725847 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 --noerrdialogs --ozone-platform=headless --ozone-override-screen-size=800,600 --use-angle=swiftshader-webgl http://localhost:9876/?id=21725847` (dns block)
> - Triggering command: `/opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4156 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-81211339 --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,7466260447177359141,14053898345622560324,262144 --disable-features=PaintHolding --variations-seed-version --trace-process-track-uuid=3190708989122997041` (dns block)
>
> If you need me to access, download, or install something from one of these locations, you can either:
>
> - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/THoguet/random-lol/settings/copilot/coding_agent) (admins only)
>
> </details>
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
> ## Feature Request: Online Multiplayer Functionality
>
> We need to extend the existing app with an **online multiplayer mode**.
> This is not a new app — it’s an additional functionality that will change parts of the implementation.
>
> ### Requirements
>
> - **Interface**
> - Keep the current interface mostly identical for now (we’ll adjust later as needed).
>
> - **Multiplayer System**
> - Add support for multiple players connected online.
> - Each player can **select their lane**, and all clients must stay **synchronized in real time**.
>
> - **Networking**
> - Use **WebSockets** for real-time synchronization.
> - When a player **rerolls their lane**, the computation should happen on a **Node.js server**, which then **dispatches the updated state** to all connected clients.
>
> - **Rooms**
> - Implement a **room system** with the following actions:
> - **Create** a room
> - **Join** a room
> - **Leave** a room
> - **Delete** a room (automatically when empty or when the owner leaves)
> - Each room should have its own isolated state and synchronized player data.
>
> - **Server Responsibilities**
> - Manage all synchronization, state updates, and event broadcasting.
> - Ensure consistency between clients and handle reconnections gracefully.
>
> ### Notes
>
> - Focus on building a solid architecture for WebSocket-based state synchronization.
> - Keep UI changes minimal; prioritize backend and communication logic first.
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
changed title from {-[WIP] Ad-}d online multiplayer {-functionality -}with room system to {+Add WebSocket-base+}d online multiplayer with room system
changed title from **{-[WIP] Ad-}d online multiplayer {-functionality -}with room system** to **{+Add WebSocket-base+}d online multiplayer with room system**
Source requested reviewers: admin-backup (GitLab user 3).
The linked history record retains original IDs and source dates. No historical identity was merged into nessar or granted new authentication permissions.
<!-- gitlab-to-gitea-assignment:20:MergeRequest:15 -->
Preserved source assignment/reviewer metadata. Historical accounts stay inactive.
Source requested reviewers: admin-backup (GitLab user 3).
The linked history record retains original IDs and source dates. No historical identity was merged into nessar or granted new authentication permissions.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements real-time multiplayer mode where players join rooms via 6-character codes and synchronize champion selections, lane toggles, and rerolls across all clients.
Server (Socket.io + Express)
Client (Angular + Signals)
isMultiplayerMode()is true, otherwise uses local stateUI
Protocol
Client→Server:
createRoom,joinRoom,leaveRoom,toggleLane,rerollLane,rollAllAssignmentsServer→Client:
roomState(full sync),playerJoined,playerLeft,errorRoom state includes assignments map, disabled lanes set, reroll bank, and player list with optional lane selections.
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.
assigned to
@Copilotassigned to
@THoguetchanged title from {-[WIP] Ad-}d online multiplayer {-functionality -}with room system to {+Add WebSocket-base+}d online multiplayer with room system
requested review from
@THoguetPreserved source assignment/reviewer metadata. Historical accounts stay inactive.
Source requested reviewers: admin-backup (GitLab user 3).
The linked history record retains original IDs and source dates. No historical identity was merged into nessar or granted new authentication permissions.
Preserved GitLab !15 history: source attribution, exact timestamps, discussion grouping and review positions. Historical diff snapshots: 2; visible source notes: 4.
gitlab-20-mergerequest-15.md (SHA-256
b202a1b413a801405dacf041732c5c222728538a1aeb13e829d2a74191cd55d6)gitlab-20-mergerequest-15.json (SHA-256
282081936a506dd23d689b3ed75ceb06564697be6dd7d6a58ddd8b7cab90662e)Old CI job execution and reports are omitted. Original source files and rollback backups remain protected.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.