12 KiB
type, title, description, tags, openwiki
| type | title | description | tags | openwiki | |||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Workflow | Slip & Challenge Workflows | Real-time game loops covering authentication, lobby creation, card dealing, phrase slipping, slip challenging, challenge resolution, and offline action queuing. |
|
|
Slip & Challenge Workflows
This document outlines the core real-time game workflows in SlipItIn, detailing how client MVVM ViewModels, WeakReferenceMessenger, backend SignalR hubs, database services, and offline action queues interact.
These workflows execute state transitions on domain entities, invoke real-time methods in the ASP.NET Core GameHub and client SignalRService, are implemented across backend and client services mapped in the source map, and are verified using tests described in the operations runbook.
1. Game Setup & Lobby Workflow
[Client App] LoginVM.LoginAsync() ──> REST Auth ──> Save Token in SecureStorage ──> SignalR.ConnectAsync()
│
[Host] LobbyVM.CreateLobbyAsync() ──> SignalR.CreateLobbyAsync() ──> Server Generates Code ──┤
│
[Player] LobbyVM.JoinLobbyAsync() ──> SignalR.JoinLobbyAsync(code) ──────────────────────────┤
│
[Player] LobbyVM.SetReadyAsync() ──> SignalR.PlayerReadyAsync() ─────────────────────────────┼──> Broadcast GameStateUpdated
│
[Host] LobbyVM.StartGameAsync() ──> SignalR.StartGameAsync() ──> Deal 5 Cards / Player ──────┴──> Unicast PlayerHandUpdated
- Authentication & Session Setup: Player logs in via
LoginPage(LoginViewModel.LoginAsync()).ApiServiceposts credentials to/api/auth/login.AuthSessionServicepersists the token toSecureStorage.SignalRService.ConnectAsync(token)opens a WebSocket connection to/hubs/game. - Lobby Creation: Host invokes
LobbyViewModel.CreateLobbyAsync().SignalRService.CreateLobbyAsync()triggersGameHub.CreateLobby().GameService.CreateGameAsync()creates aGamerecord (Status = Lobby), generates a random 6-characterLobbyCode, adds host togame.Players, and registers host'sConnectionId. Caller receivesLobbyCreatedmessage viaWeakReferenceMessenger. - Joining Lobby: Opponents call
LobbyViewModel.JoinLobbyAsync().SignalRService.JoinLobbyAsync(code)validates game status and capacity.GameService.JoinGameAsync()assigns player to game, registersConnectionId, adds player to SignalR grouplobbyCode, and broadcastsPlayerJoined(GameStateDto) to all participants. - Player Ready: Players invoke
LobbyViewModel.SetReadyAsync().GameHub.PlayerReady()verifies access and broadcastsGameStateUpdated. - Game Start & Card Dealing: The host invokes
LobbyViewModel.StartGameAsync().GameHub.StartGame()verifies host identity (ValidateHostAccessAsync).GameService.StartGameAsync()transitionsGameStatustoInProgressand createsGameRound1 (RoundStatus.Active).GameService.DealCardsAsync()selects 5 random phrases per player, createsPlayerCardrecords, and saves them to PostgreSQL.- SignalR broadcasts
GameStartedto the group (triggering client navigation toGameBoardPage). - SignalR sends private
PlayerHandUpdatednotifications (PlayerHandDto) individually to each player'sConnectionId.
2. Phrase Slipping & Offline Queueing Workflow
During real-life conversation or chat, a player speaks or types one of their secret phrases and marks it as used in the app:
- User Action: Player clicks Submit Slip on
GameBoardPage(GameBoardViewModel.SubmitSlipAsync(card)). - Online / Offline Branching:
- If Connected (
_signalR.IsConnected): CallsSignalRService.SubmitSlipAsync(gameId, playerId, cardId)directly.GameHub.SubmitSlip()validates ownership and updatesPlayerCard.IsUsed = true.GameHubbroadcastsSlipSubmittedto the lobby group. - If Offline (
!_signalR.IsConnected): CallsGameStateService.EnqueueActionAsync("SubmitSlip", gameId, playerId, cardId). Action is serialized intoQueuedGameActionand saved inPreferences. UI updates status to"Offline: Aktion wurde zwischengespeichert."
- If Connected (
3. Slip Challenge & Resolution Workflow
If an opponent suspects that a submitted phrase or recent conversation statement was an invalid slip, they can challenge the slipper.
Step 1: Challenge Initiation
- Challenger clicks Challenge on
GameBoardPage(GameBoardViewModel.ChallengeCardAsync(card)). - If online, calls
SignalRService.ChallengeSlipAsync(gameId, playerId, cardId). GameHub.ChallengeSlip()verifies challenger identity (ValidatePlayerAccessAsync).GameService.CreateChallengeAsync()creates aSlipChallengeentity withChallengeStatus.Pending.GameHubbroadcastsSlipChallenged(SlipChallengeDto) to the group and unicastsChallengeReceiveddirectly to target player'sConnectionId.- Client target player sees
ChallengeNotificationOverlayonGameBoardPage.
Step 2: Challenge Resolution
The accused player responds via ChallengeNotificationOverlay:
- Accused player calls
GameBoardViewModel.ResolveChallengeAsync(challengeId, approved). SignalRService.ResolveChallengeAsync(challengeId, approved)callsGameHub.ResolveChallenge().GameHubexecutesValidateChallengeTargetAccessAsync(challengeId, authUserId)to guarantee only the accused player can resolve the challenge.GameService.ResolveChallengeAsync()executes penalty logic:approved = true(Justified Accusation / Legitimate Catch): The accused admits the phrase was a fake slip.SlipChallenge.Statusis set toApproved. The card remains with accused player.approved = false(False Accusation / Wrong Penalty): The accused denies the charge.SlipChallenge.Statusis set toRejected. As a penalty for making a false accusation, the phrase card is reassigned to the challenger:targetCard.PlayerId = challenge.ChallengingPlayerId.
GameHubbroadcastsChallengeResolvedto the group.
sequenceDiagram
autonumber
actor Slipper as Accused Player A
actor Challenger as Challenger Player B
participant GameBoardVM as GameBoardViewModel
participant Hub as GameHub
participant Service as GameService
participant DB as SlipItInDbContext
Note over Slipper, Challenger: Active Gameplay
Slipper->>GameBoardVM: SubmitSlipAsync(card)
GameBoardVM->>Hub: SubmitSlip(gameId, playerAId, cardId)
Hub->>Service: SubmitSlipAsync()
Service->>DB: Set PlayerCard.IsUsed = true
Hub-->>Challenger: Broadcast "SlipSubmitted"
Note over Challenger: Suspects invalid phrase
Challenger->>GameBoardVM: ChallengeCardAsync(card)
GameBoardVM->>Hub: ChallengeSlip(gameId, playerBId, cardId)
Hub->>Service: CreateChallengeAsync()
Service->>DB: Insert SlipChallenge (Status = Pending)
Hub-->>Challenger: Broadcast "SlipChallenged"
Hub-->>Slipper: Unicast "ChallengeReceived"
Note over Slipper: Notification Overlay Displays
Slipper->>GameBoardVM: ResolveChallengeAsync(challengeId, approved)
GameBoardVM->>Hub: ResolveChallenge(challengeId, approved)
Hub->>Hub: ValidateChallengeTargetAccessAsync()
Hub->>Service: ResolveChallengeAsync()
alt approved == false (False Accusation)
Service->>DB: Reassign card.PlayerId = PlayerBId
Service->>DB: Set Challenge.Status = Rejected
else approved == true (Justified Catch)
Service->>DB: Set Challenge.Status = Approved
end
Hub-->>Challenger: Broadcast "ChallengeResolved"
Sequence diagram showing phrase submission, challenge initiation, target notification, and penalty resolution.
4. Offline Action Queueing & Reconnection Resync Workflow
When network connection is temporarily interrupted during gameplay, SlipItIn ensures action durability and smooth state synchronization:
sequenceDiagram
autonumber
actor User as Player
participant GameBoardVM as GameBoardViewModel
participant GameStateSvc as GameStateService
participant LocalStorage as LocalStorageService
participant SignalRSvc as SignalRService
participant Hub as GameHub
Note over User, SignalRSvc: Network Disconnected (Offline)
User->>GameBoardVM: SubmitSlipAsync(card)
GameBoardVM->>GameStateSvc: EnqueueActionAsync("SubmitSlip", args)
GameStateSvc->>LocalStorage: SaveQueuedActionsAsync(queuedList)
LocalStorage-->>GameBoardVM: Saved to Preferences
Note over SignalRSvc: Network Restored
SignalRSvc->>Hub: Automatic Reconnect (Exponential Backoff)
SignalRSvc->>GameStateSvc: ConnectionStateChanged(true)
GameStateSvc->>GameStateSvc: ResyncAsync()
GameStateSvc->>SignalRSvc: RequestGameStateAsync(gameId)
SignalRSvc->>Hub: RequestGameState(gameId)
Hub-->>GameStateSvc: GameStateUpdated (Fresh DTO)
GameStateSvc->>GameStateSvc: FlushQueuedActionsAsync()
loop For each QueuedGameAction
GameStateSvc->>SignalRSvc: SendQueuedActionAsync(action)
SignalRSvc->>Hub: InvokeCoreAsync(action.Method, action.Args)
end
GameStateSvc->>LocalStorage: ClearQueuedActionsAsync()
Sequence diagram showing offline action queueing in local preferences, automatic reconnection, state request, and queued action replay.
5. SignalR Hub Event Reference
Summary of server-to-client events emitted by GameHub.cs and handled by SignalRService:
| Event Name | Scope | Payload | Trigger |
|---|---|---|---|
LobbyCreated |
Caller | { GameId, LobbyCode } |
Host calls CreateLobby() |
PlayerJoined |
Group | GameStateDto |
Player calls JoinLobby() |
GameStateUpdated |
Group | GameStateDto |
Player calls PlayerReady() or RequestGameState() |
GameStarted |
Group | { GameId } |
Host calls StartGame() |
PlayerHandUpdated |
Unicast | PlayerHandDto |
Hand dealt or updated |
SlipSubmitted |
Group | { PlayerId, CardId, Success } |
Player calls SubmitSlip() |
SlipChallenged |
Group | SlipChallengeDto |
Opponent calls ChallengeSlip() |
ChallengeReceived |
Unicast | SlipChallengeDto |
Direct alert to target player |
ChallengeResolved |
Group | SlipChallengeDto |
Target calls ResolveChallenge() |
Error |
Caller | { Message } |
Any authorization or engine exception |
6. Guidance for Future Agents & Developers
- When to Consult: Refer to this document when adding new game interactions, modifying UI challenge flows, changing reconnection strategies, or replaying offline actions.
- Invariants:
FlushQueuedActionsAsync()MUST execute in strictly sequential order based onCreatedAtUtc.ResolveChallengeMUST throwUnauthorizedAccessExceptionif invoked by anyone other than the target player.
- Primary Source Files:
SlipItIn/ViewModels/GameBoardViewModel.csSlipItIn/Services/GameStateService.csSlipItIn/Services/SignalRService.csSlipItIn.Server/Hubs/GameHub.cs
- Minimal Validation Command:
dotnet build SlipItIn.slnx