Truth or Dare API Documentation
A comprehensive guide to implementing the Truth or Dare game in your application
Game Engine Logic
Overview
The Truth or Dare game allows partners to engage in a fun, interactive experience that can help deepen their connection through answering personal questions or performing playful dares. The game supports both online mode (between connected partners) and offline mode (for local play).
Game Flow
The Truth or Dare game connects partners through a turn-based experience where they answer personal questions or perform dares.
- Connection: Users connect with their unique IDs to join the game.
- Invitation: One partner sends a game invitation to the other.
- Acceptance: The invited partner accepts the invitation to start the game.
- Taking Turns: Players alternate turns, with each turn following this sequence:
- Active player chooses "Truth" or "Dare"
- System presents a random question or challenge
- Player answers and submits their response
- Turn passes to their partner
- Viewing & Reacting: When it's your turn, you first see your partner's previous answer and can react with an emoji before proceeding to your challenge.
- Waiting State: After completing your turn, you'll see a "Waiting for your partner..." screen that displays any reaction they gave to your answer.
- Game Completion: The game ends after a set number of turns (default: 10), or when players choose to end it.
API Reference
Follow these steps to implement the Truth or Dare game in your Swift application. Each step corresponds to a specific API endpoint and game state.
Step 1: Check Partner Status
Before starting a game, verify that the user has a connected partner.
GET /api/games/truth-or-dare/partner-status
Query Parameters:
userId- The unique ID of the user
Success Response:
{
"hasPartner": true,
"partnerId": "user_partner123",
"partnerName": "Partner Name",
"connectionStatus": "connected"
}Error Response:
{
"error": "Failed to check partner status",
"details": "Error message details"
}Swift Implementation:
// Function to check partner status
func checkPartnerStatus(userId: String) async throws -> PartnerStatus {
guard let url = URL(string: "https://your-api-domain.com/api/games/truth-or-dare/partner-status?userId=\(userId)") else {
throw NetworkError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
do {
let partnerStatus = try JSONDecoder().decode(PartnerStatus.self, from: data)
return partnerStatus
} catch {
throw NetworkError.decodingError(error)
}
}
// Partner Status model
struct PartnerStatus: Codable {
let hasPartner: Bool
let partnerId: String?
let partnerName: String?
let connectionStatus: String
}Step 2: Update and Check Game Presence
Update the user's online status and check if their partner is online to play the game.
POST /api/games/truth-or-dare/game-presence
Request Body:
{
"userId": "user123",
"isInGame": true
}Success Response:
{
"success": true
}GET /api/games/truth-or-dare/game-presence
Query Parameters:
userId- The unique ID of the userpartnerId- The unique ID of the partner
Success Response:
{
"userOnline": true,
"partnerOnline": true,
"lastActive": {
"userId": 1618047632000,
"partnerId": 1618047690000
}
}Swift Implementation:
// Function to update presence
func updateGamePresence(userId: String) async throws -> Bool {
guard let url = URL(string: "https://your-api-domain.com/api/games/truth-or-dare/game-presence") else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body = ["userId": userId, "isInGame": true]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let success = json["success"] as? Bool {
return success
} else {
throw NetworkError.invalidData
}
} catch {
throw NetworkError.decodingError(error)
}
}
// Function to check game presence
func checkGamePresence(userId: String, partnerId: String) async throws -> GamePresence {
guard let url = URL(string: "https://your-api-domain.com/api/games/truth-or-dare/game-presence?userId=\(userId)&partnerId=\(partnerId)") else {
throw NetworkError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
do {
let gamePresence = try JSONDecoder().decode(GamePresence.self, from: data)
return gamePresence
} catch {
throw NetworkError.decodingError(error)
}
}
// Game Presence model
struct GamePresence: Codable {
let userOnline: Bool
let partnerOnline: Bool
let lastActive: [String: TimeInterval]
}Step 3: Check Current Game State
Check if there's an existing game in progress between the user and their partner.
GET /api/games/truth-or-dare/game-state
Query Parameters:
userId- The unique ID of the userpartnerId- The unique ID of the partner
Success Response (Game in Progress):
{
"_id": "game123",
"userId": "user123",
"partnerId": "partner456",
"gameType": "truth_or_dare",
"currentState": {
"isGameOver": false,
"gameStatus": "active",
"currentTurn": "user123",
"turnCount": 3,
"rounds": [...],
"currentChallenge": {
"type": "truth",
"questionId": "question123",
"question": "What's your biggest fear?"
},
"settings": {
"turnLimit": 10,
"intimacyLevel": 3,
"categories": ["fun", "romantic"]
}
},
"lastUpdated": 1618047690000
}Success Response (No Game):
null
Swift Implementation:
// Function to check game state
func checkGameState(userId: String, partnerId: String) async throws -> GameState? {
guard let url = URL(string: "https://your-api-domain.com/api/games/truth-or-dare/game-state?userId=\(userId)&partnerId=\(partnerId)") else {
throw NetworkError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
// Check if response is null (no game)
if data.count == 4, let dataString = String(data: data, encoding: .utf8), dataString == "null" {
return nil
}
do {
let gameState = try JSONDecoder().decode(GameState.self, from: data)
return gameState
} catch {
throw NetworkError.decodingError(error)
}
}
// Game State model (partial example)
struct GameState: Codable {
let _id: String
let userId: String
let partnerId: String
let gameType: String
let currentState: CurrentGameState
let lastUpdated: TimeInterval
}
struct CurrentGameState: Codable {
let isGameOver: Bool
let gameStatus: String
let currentTurn: String
let turnCount: Int
let rounds: [GameRound]
let currentChallenge: Challenge?
let settings: GameSettings
}
// Add other necessary model structs...Step 4: Check Pending Invitations
Check if the user has any pending game invitations from their partner.
GET /api/games/truth-or-dare/invitations
Query Parameters:
userId- The unique ID of the user
Success Response:
{
"success": true,
"invitations": [
{
"_id": "game123",
"userId": "partner456",
"partnerId": "user123",
"gameType": "truth_or_dare",
"currentState": {
"gameStatus": "invitation_pending",
"invitedBy": "partner456",
"invitedAt": 1618047690000,
"expiresAt": 1618047990000,
"settings": {
"turnLimit": 10,
"intimacyLevel": 3,
"categories": ["fun", "romantic"]
}
}
}
]
}Swift Implementation:
// Function to check pending invitations
func checkPendingInvitations(userId: String) async throws -> [GameInvitation] {
guard let url = URL(string: "https://your-api-domain.com/api/games/truth-or-dare/invitations?userId=\(userId)") else {
throw NetworkError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
do {
let result = try JSONDecoder().decode(InvitationsResponse.self, from: data)
return result.success ? result.invitations : []
} catch {
throw NetworkError.decodingError(error)
}
}
// Invitations Response model
struct InvitationsResponse: Codable {
let success: Bool
let invitations: [GameInvitation]
let error: String?
}
struct GameInvitation: Codable {
let _id: String
let userId: String
let partnerId: String
let gameType: String
let currentState: InvitationState
}
struct InvitationState: Codable {
let gameStatus: String
let invitedBy: String
let invitedAt: TimeInterval
let expiresAt: TimeInterval
let settings: GameSettings
}Step 5: Send Game Invitation
If no game exists and there are no pending invitations, send a game invitation to the partner.
POST /api/games/truth-or-dare/invitations
Request Body:
{
"userId": "user123",
"partnerId": "partner456",
"settings": {
"turnLimit": 10,
"intimacyLevel": 3,
"categories": ["fun", "romantic"]
}
}Success Response:
{
"gameId": "game123",
"success": true
}Error Response:
{
"error": "Your partner is not online right now"
}Swift Implementation:
// Function to send game invitation
func sendGameInvitation(userId: String, partnerId: String) async throws -> String {
guard let url = URL(string: "https://your-api-domain.com/api/games/truth-or-dare/invitations") else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let settings: [String: Any] = [
"turnLimit": 10,
"intimacyLevel": 3,
"categories": ["fun", "romantic"]
]
let body: [String: Any] = [
"userId": userId,
"partnerId": partnerId,
"settings": settings
]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
if httpResponse.statusCode != 200 {
// Try to decode error message
if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
throw NetworkError.apiError(errorResponse.error)
} else {
throw NetworkError.invalidResponse
}
}
do {
let invitationResponse = try JSONDecoder().decode(InvitationResponse.self, from: data)
if invitationResponse.success {
return invitationResponse.gameId
} else {
throw NetworkError.apiError("Unknown error")
}
} catch {
throw NetworkError.decodingError(error)
}
}
// Response models
struct InvitationResponse: Codable {
let gameId: String
let success: Bool
}
struct ErrorResponse: Codable {
let error: String
let details: String?
}Step 6: Respond to Game Invitation
Accept or decline a pending game invitation.
POST /api/games/truth-or-dare/invitations/{gameId}
URL Parameters:
gameId- The ID of the game invitation
Request Body:
{
"userId": "user123",
"action": "accept" // or "decline"
}Success Response:
{
"success": true,
"gameState": { /* Game state object */ }
}Swift Implementation:
// Function to respond to game invitation
func respondToInvitation(gameId: String, userId: String, accept: Bool) async throws -> GameState {
guard let url = URL(string: "https://your-api-domain.com/api/games/truth-or-dare/invitations/\(gameId)") else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"userId": userId,
"action": accept ? "accept" : "decline"
]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
do {
let responseObj = try JSONDecoder().decode(InvitationResponseWithState.self, from: data)
return responseObj.gameState
} catch {
throw NetworkError.decodingError(error)
}
}
// Response model
struct InvitationResponseWithState: Codable {
let success: Bool
let gameState: GameState
}Step 7: Game Actions (Core Gameplay)
Once a game is active, players use this endpoint to perform all gameplay actions including making choices, completing challenges, adding reactions, and ending the game.
POST /api/games/truth-or-dare/games/{gameId}/actions
URL Parameters:
gameId- The ID of the active game
Action: Make Choice (Truth or Dare)
{
"userId": "user123",
"action": "makeChoice",
"choice": "truth" // or "dare"
}Action: Complete Challenge (Answer/Submit)
{
"userId": "user123",
"action": "completeChallenge",
"answer": "My biggest fear is heights",
"completed": true,
"markSeen": false // Set to true when viewing partner's answer
}Action: Add Reaction to Partner's Answer
{
"userId": "user123",
"action": "addReaction",
"roundIndex": 2,
"reaction": "😂"
}Action: End Game
{
"userId": "user123",
"action": "endGame",
"reason": "ended_by_player"
}Success Response:
{
"success": true,
"challenge": { // Only for makeChoice action
"_id": "question123",
"question": "What's your biggest fear?",
"category": "deep",
"intimacyLevel": 3
}
}Swift Implementation:
// Function to perform game actions
func performGameAction(gameId: String, userId: String, action: GameAction) async throws -> GameActionResponse {
guard let url = URL(string: "https://your-api-domain.com/api/games/truth-or-dare/games/\(gameId)/actions") else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body = try action.toRequestBody(userId: userId)
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
do {
let actionResponse = try JSONDecoder().decode(GameActionResponse.self, from: data)
return actionResponse
} catch {
throw NetworkError.decodingError(error)
}
}
// Game Action enum
enum GameAction {
case makeChoice(choice: String)
case completeChallenge(answer: String, completed: Bool, markSeen: Bool)
case addReaction(roundIndex: Int, reaction: String)
case endGame(reason: String?)
func toRequestBody(userId: String) throws -> [String: Any] {
var body: [String: Any] = ["userId": userId]
switch self {
case .makeChoice(let choice):
body["action"] = "makeChoice"
body["choice"] = choice
case .completeChallenge(let answer, let completed, let markSeen):
body["action"] = "completeChallenge"
body["answer"] = answer
body["completed"] = completed
body["markSeen"] = markSeen
case .addReaction(let roundIndex, let reaction):
body["action"] = "addReaction"
body["roundIndex"] = roundIndex
body["reaction"] = reaction
case .endGame(let reason):
body["action"] = "endGame"
if let reason = reason {
body["reason"] = reason
}
}
return body
}
}
// Response models
struct GameActionResponse: Codable {
let success: Bool
let challenge: Challenge?
let isGameOver: Bool?
}
struct Challenge: Codable {
let _id: String
let question: String?
let dare: String?
let category: String
let intimacyLevel: Int
}Step 7.5: Get Truth or Dare Game Data (with Rounds)
This API endpoint allows you to retrieve complete game data including all rounds, making it easy to display game history, current state, and detailed round information. This is particularly useful for showing game summaries, displaying previous answers, and implementing game replay features.
POST /api/truth-or-dare
Use Cases:
- Retrieve current game state with all completed rounds
- Get specific game by ID for detailed viewing
- Load game between two specific users
- Display game history and summaries
- Show previous answers and reactions
Request Body - Get Most Recent Game:
{
"userId": "user123"
}Request Body - Get Game Between Specific Users:
{
"userId": "user123",
"partnerId": "partner456"
}Request Body - Get Specific Game by ID:
{
"userId": "user123",
"gameId": "game_abc123def456"
}Success Response (Game Found):
{
"success": true,
"game": {
"_id": "game_abc123def456",
"userId": "user123",
"partnerId": "partner456",
"gameType": "truth_or_dare",
"currentState": {
"isGameOver": false,
"gameStatus": "active",
"currentTurn": "partner456",
"turnCount": 4,
"settings": {
"turnLimit": 10,
"intimacyLevel": 3,
"categories": ["fun", "romantic"]
},
"rounds": [
{
"player": "user123",
"choice": "truth",
"questionId": "question_123",
"question": "What's your biggest fear?",
"answer": "Spiders and heights",
"completed": true,
"timestamp": 1618047690000,
"reaction": "😱",
"reactionTimestamp": 1618047720000,
"seen": true,
"seenAt": 1618047715000
},
{
"player": "partner456",
"choice": "dare",
"dareId": "dare_456",
"dare": "Send a silly selfie to your crush",
"answer": "Done! Sent a funny photo",
"completed": true,
"timestamp": 1618047750000,
"reaction": "😂",
"reactionTimestamp": 1618047780000,
"seen": true,
"seenAt": 1618047775000
}
],
"currentChallenge": null,
"lastAction": {
"type": "challenge_completed",
"userId": "partner456",
"timestamp": 1618047750000
}
},
"lastUpdated": 1618047750000,
"playersArray": ["user123", "partner456"],
"rounds": [
// Same rounds array duplicated here for easy access
]
}
}Success Response (No Game Found):
{
"success": true,
"game": null
}Error Response:
{
"success": false,
"error": "User not found",
"game": null
}Swift Implementation:
// Function to get Truth or Dare game data
func getTruthOrDareGame(userId: String, partnerId: String? = nil, gameId: String? = nil) async throws -> TruthOrDareGameResponse {
guard let url = URL(string: "https://your-api-domain.com/api/truth-or-dare") else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
var body: [String: Any] = ["userId": userId]
if let partnerId = partnerId {
body["partnerId"] = partnerId
}
if let gameId = gameId {
body["gameId"] = gameId
}
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
do {
let gameResponse = try JSONDecoder().decode(TruthOrDareGameResponse.self, from: data)
return gameResponse
} catch {
throw NetworkError.decodingError(error)
}
}
// Response models
struct TruthOrDareGameResponse: Codable {
let success: Bool
let game: TruthOrDareGame?
let error: String?
}
struct TruthOrDareGame: Codable {
let _id: String
let userId: String
let partnerId: String
let gameType: String
let currentState: GameCurrentState
let lastUpdated: TimeInterval
let playersArray: [String]
let rounds: [GameRound] // Duplicate rounds for easy access
}
struct GameCurrentState: Codable {
let isGameOver: Bool
let gameStatus: String
let currentTurn: String
let turnCount: Int
let settings: GameSettings
let rounds: [GameRound]
let currentChallenge: CurrentChallenge?
let lastAction: LastAction
}
struct GameRound: Codable {
let player: String
let choice: String
let questionId: String?
let dareId: String?
let question: String?
let dare: String?
let answer: String
let completed: Bool
let timestamp: TimeInterval
let reaction: String?
let reactionTimestamp: TimeInterval?
let seen: Bool?
let seenAt: TimeInterval?
}
struct CurrentChallenge: Codable {
let type: String
let questionId: String?
let dareId: String?
let question: String?
let dare: String?
}
struct LastAction: Codable {
let type: String
let userId: String
let timestamp: TimeInterval
let choice: String?
let questionId: String?
let dareId: String?
}
struct GameSettings: Codable {
let turnLimit: Int
let intimacyLevel: Int
let categories: [String]
}
// Usage examples:
// Get current game for user
let currentGame = try await getTruthOrDareGame(userId: "user123")
// Get game between specific users
let gameWithPartner = try await getTruthOrDareGame(userId: "user123", partnerId: "partner456")
// Get specific game by ID
let specificGame = try await getTruthOrDareGame(userId: "user123", gameId: "game_abc123def456")💡 Pro Tips
- Use this API to display game history and summaries
- The rounds array contains complete information about each turn including reactions
- Check the `seen` property to know if a round answer has been viewed
- Use `gameStatus` to determine the current state: "active", "paused", "invitation_pending"
- The `currentChallenge` field shows if a player is in the middle of answering
- Timestamp values are in milliseconds since epoch
Step 8: Complete Game Flow Implementation
Here's how to implement the complete game flow in your Swift application, combining all the APIs to create a seamless Truth or Dare experience.
Complete Swift Implementation:
class TruthOrDareGameManager: ObservableObject {
@Published var gameState: GameState?
@Published var currentScreen: GameScreen = .idle
@Published var errorMessage: String?
private let userId: String
private var partnerId: String?
private var gameId: String?
private var presenceTimer: Timer?
enum GameScreen {
case idle
case connecting
case inviting
case waitingForPartner
case playing
case viewingPartnerAnswer
case waitingForReaction
case gameOver
}
init(userId: String) {
self.userId = userId
}
// MARK: - Main Game Flow
func startGame() async {
do {
// Step 1: Check partner status
let partnerStatus = try await checkPartnerStatus(userId: userId)
guard partnerStatus.hasPartner else {
errorMessage = "No partner connected"
return
}
partnerId = partnerStatus.partnerId
// Step 2: Update presence
_ = try await updateGamePresence(userId: userId)
startPresenceHeartbeat()
// Step 3: Check existing game
if let existingGame = try await checkGameState(userId: userId, partnerId: partnerId!) {
gameState = existingGame
determineCurrentScreen()
return
}
// Step 4: Check pending invitations
let invitations = try await checkPendingInvitations(userId: userId)
if !invitations.isEmpty {
// Handle pending invitation
currentScreen = .waitingForPartner
return
}
// Step 5: Send invitation
currentScreen = .inviting
let invitation = try await sendGameInvitation(userId: userId, partnerId: partnerId!)
gameId = invitation.gameId
currentScreen = .waitingForPartner
} catch {
errorMessage = error.localizedDescription
}
}
func acceptInvitation(_ invitation: GameInvitation) async {
do {
let gameState = try await respondToInvitation(
gameId: invitation._id,
userId: userId,
accept: true
)
self.gameState = gameState
self.gameId = invitation._id
currentScreen = .playing
} catch {
errorMessage = error.localizedDescription
}
}
func makeChoice(_ choice: String) async {
guard let gameId = gameId else { return }
do {
let response = try await performGameAction(
gameId: gameId,
userId: userId,
action: .makeChoice(choice: choice)
)
// Refresh game state to get the challenge
await refreshGameState()
} catch {
errorMessage = error.localizedDescription
}
}
func submitAnswer(_ answer: String, completed: Bool = true) async {
guard let gameId = gameId else { return }
do {
let response = try await performGameAction(
gameId: gameId,
userId: userId,
action: .completeChallenge(answer: answer, completed: completed, markSeen: false)
)
await refreshGameState()
if response.isGameOver == true {
currentScreen = .gameOver
} else {
currentScreen = .waitingForPartner
}
} catch {
errorMessage = error.localizedDescription
}
}
func viewPartnerAnswerAndContinue(markSeen: Bool = true) async {
guard let gameId = gameId else { return }
do {
_ = try await performGameAction(
gameId: gameId,
userId: userId,
action: .completeChallenge(answer: "", completed: true, markSeen: markSeen)
)
await refreshGameState()
currentScreen = .playing
} catch {
errorMessage = error.localizedDescription
}
}
func addReaction(_ reaction: String, to roundIndex: Int) async {
guard let gameId = gameId else { return }
do {
_ = try await performGameAction(
gameId: gameId,
userId: userId,
action: .addReaction(roundIndex: roundIndex, reaction: reaction)
)
await refreshGameState()
} catch {
errorMessage = error.localizedDescription
}
}
func endGame() async {
guard let gameId = gameId else { return }
do {
_ = try await performGameAction(
gameId: gameId,
userId: userId,
action: .endGame(reason: "ended_by_player")
)
currentScreen = .gameOver
stopPresenceHeartbeat()
} catch {
errorMessage = error.localizedDescription
}
}
// MARK: - Helper Methods
private func refreshGameState() async {
guard let partnerId = partnerId else { return }
do {
gameState = try await checkGameState(userId: userId, partnerId: partnerId)
determineCurrentScreen()
} catch {
errorMessage = error.localizedDescription
}
}
private func determineCurrentScreen() {
guard let gameState = gameState else {
currentScreen = .idle
return
}
if gameState.currentState.isGameOver {
currentScreen = .gameOver
return
}
// Check if partner just completed a challenge and current user needs to see it
let lastRound = gameState.currentState.rounds.last
let needsToSeePartnerAnswer = gameState.currentState.currentTurn == userId &&
lastRound?.player != userId &&
lastRound?.seen != true
if needsToSeePartnerAnswer {
currentScreen = .viewingPartnerAnswer
} else if gameState.currentState.currentTurn == userId {
currentScreen = .playing
} else {
currentScreen = .waitingForPartner
}
}
private func startPresenceHeartbeat() {
presenceTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { _ in
Task {
try? await self.updateGamePresence(userId: self.userId)
}
}
}
private func stopPresenceHeartbeat() {
presenceTimer?.invalidate()
presenceTimer = nil
}
deinit {
stopPresenceHeartbeat()
}
}Step 9: UI Implementation Guidelines
Based on the game flow, here are the different UI screens you'll need to implement in your Swift app:
1. Connection Screen (.idle)
- User ID input field
- Connect button
- Display partner status once connected
- Show "Invite to Play" button when both users are online
2. Game Choice Screen (.playing - choice phase)
- Two buttons: "Truth" and "Dare"
- Game progress indicator (current turn / total turns)
- Option to end game early
3. Challenge Screen (.playing - answer phase)
- Display the truth question or dare challenge
- Text input for answer
- Submit button
- For dares: "Can't Do It" button as alternative
4. Partner Answer Review (.viewingPartnerAnswer)
- Display partner's challenge and answer
- Emoji reaction buttons (😂, 😮, ❤️, 👍, etc.)
- "Continue to My Turn" button
5. Waiting Screen (.waitingForPartner)
- Loading indicator
- "Waiting for partner..." message
- Show partner's reaction to your answer (if available)
- Option to end game
6. Game Over Screen (.gameOver)
- Game summary with all rounds played
- Each round showing question/dare, answer, and reactions
- "Play Again" button
- "Back to Menu" button
Step 10: Error Handling and Edge Cases
Important considerations for a robust implementation:
Connection Issues
- Handle partner going offline during game
- Implement reconnection logic
- Show appropriate waiting/paused states
- Auto-resume when both players are back online
Game State Synchronization
- Poll game state regularly during active gameplay
- Handle out-of-turn actions gracefully
- Validate current player's turn before allowing actions
- Show loading states during API calls
Invitation Management
- Handle invitation expiration (5 minutes timeout)
- Check for existing games before sending new invitations
- Allow declining invitations gracefully
- Show pending invitation notifications
Data Validation
- Validate user IDs before making API calls
- Handle empty or invalid responses
- Implement proper error messaging for users
- Add input validation for answers and reactions
Need help?
If you have any questions about implementing the Truth or Dare game in your application, please contact the developer support team.
Return to Documentation Home