package matrix import ( "context" "fmt" "time" ) // ======================================== // Breakpilot Drive Game Room Types // ======================================== // GameMode represents different multiplayer game modes type GameMode string const ( GameModeSolo GameMode = "solo" GameModeCoop GameMode = "coop" // 2 players, same track GameModeChallenge GameMode = "challenge" // 1v1 competition GameModeClassRace GameMode = "class_race" // Whole class competition ) // GameRoomConfig holds configuration for game rooms type GameRoomConfig struct { GameMode GameMode `json:"game_mode"` SessionID string `json:"session_id"` HostUserID string `json:"host_user_id"` HostName string `json:"host_name"` ClassName string `json:"class_name,omitempty"` MaxPlayers int `json:"max_players,omitempty"` TeacherIDs []string `json:"teacher_ids,omitempty"` EnableVoice bool `json:"enable_voice,omitempty"` } // GameRoom represents an active game room type GameRoom struct { RoomID string `json:"room_id"` SessionID string `json:"session_id"` GameMode GameMode `json:"game_mode"` HostUserID string `json:"host_user_id"` Players []string `json:"players"` CreatedAt time.Time `json:"created_at"` IsActive bool `json:"is_active"` } // GameEvent represents game events to broadcast type GameEvent struct { Type string `json:"type"` SessionID string `json:"session_id"` PlayerID string `json:"player_id"` Data interface{} `json:"data"` Timestamp time.Time `json:"timestamp"` } // GameEventType constants const ( GameEventPlayerJoined = "player_joined" GameEventPlayerLeft = "player_left" GameEventGameStarted = "game_started" GameEventQuizAnswered = "quiz_answered" GameEventScoreUpdate = "score_update" GameEventAchievement = "achievement" GameEventChallengeWon = "challenge_won" GameEventRaceFinished = "race_finished" ) // ======================================== // Game Room Management // ======================================== // CreateGameTeamRoom creates a private room for 2-4 players (Co-Op mode) func (s *MatrixService) CreateGameTeamRoom(ctx context.Context, config GameRoomConfig) (*CreateRoomResponse, error) { roomName := fmt.Sprintf("Breakpilot Drive - Team %s", config.SessionID[:8]) topic := "Co-Op Spielsession - Arbeitet zusammen!" // All players can write users := make(map[string]int) users[s.GenerateUserID(config.HostUserID)] = 50 req := CreateRoomRequest{ Name: roomName, Topic: topic, Visibility: "private", Preset: "private_chat", InitialState: []StateEvent{ { Type: "m.room.encryption", StateKey: "", Content: map[string]string{ "algorithm": "m.megolm.v1.aes-sha2", }, }, // Custom game state { Type: "breakpilot.game.session", StateKey: "", Content: map[string]interface{}{ "session_id": config.SessionID, "game_mode": string(config.GameMode), "host_id": config.HostUserID, "created_at": time.Now().UTC().Format(time.RFC3339), }, }, }, PowerLevelContentOverride: &PowerLevels{ EventsDefault: 0, // All players can send messages UsersDefault: 50, Users: users, Events: map[string]int{ "breakpilot.game.event": 0, // Anyone can send game events }, }, } return s.CreateRoom(ctx, req) } // CreateGameChallengeRoom creates a 1v1 challenge room func (s *MatrixService) CreateGameChallengeRoom(ctx context.Context, config GameRoomConfig, challengerID string, opponentID string) (*CreateRoomResponse, error) { roomName := fmt.Sprintf("Challenge: %s", config.SessionID[:8]) topic := "1v1 Wettbewerb - Mรถge der Bessere gewinnen!" allPlayers := []string{ s.GenerateUserID(challengerID), s.GenerateUserID(opponentID), } users := make(map[string]int) for _, id := range allPlayers { users[id] = 50 } req := CreateRoomRequest{ Name: roomName, Topic: topic, Visibility: "private", Preset: "private_chat", Invite: allPlayers, InitialState: []StateEvent{ { Type: "breakpilot.game.session", StateKey: "", Content: map[string]interface{}{ "session_id": config.SessionID, "game_mode": string(GameModeChallenge), "challenger_id": challengerID, "opponent_id": opponentID, "created_at": time.Now().UTC().Format(time.RFC3339), }, }, }, PowerLevelContentOverride: &PowerLevels{ EventsDefault: 0, UsersDefault: 50, Users: users, }, } return s.CreateRoom(ctx, req) } // CreateGameClassRaceRoom creates a room for class-wide competition func (s *MatrixService) CreateGameClassRaceRoom(ctx context.Context, config GameRoomConfig) (*CreateRoomResponse, error) { roomName := fmt.Sprintf("Klassenrennen: %s", config.ClassName) topic := fmt.Sprintf("Klassenrennen der %s - Alle gegen alle!", config.ClassName) // Teachers get moderator power level users := make(map[string]int) for _, teacherID := range config.TeacherIDs { users[s.GenerateUserID(teacherID)] = 100 } req := CreateRoomRequest{ Name: roomName, Topic: topic, Visibility: "private", Preset: "private_chat", InitialState: []StateEvent{ { Type: "breakpilot.game.session", StateKey: "", Content: map[string]interface{}{ "session_id": config.SessionID, "game_mode": string(GameModeClassRace), "class_name": config.ClassName, "teacher_ids": config.TeacherIDs, "created_at": time.Now().UTC().Format(time.RFC3339), }, }, }, PowerLevelContentOverride: &PowerLevels{ EventsDefault: 0, // Students can send messages UsersDefault: 10, // Default student level Users: users, Invite: 100, // Only teachers can invite Kick: 100, // Only teachers can kick Events: map[string]int{ "breakpilot.game.event": 0, // Anyone can send game events "breakpilot.game.leaderboard": 100, // Only teachers update leaderboard }, }, } return s.CreateRoom(ctx, req) } // ======================================== // Game Event Broadcasting // ======================================== // SendGameEvent sends a game event to a room func (s *MatrixService) SendGameEvent(ctx context.Context, roomID string, event GameEvent) error { event.Timestamp = time.Now().UTC() return s.sendEvent(ctx, roomID, "breakpilot.game.event", event) } // SendPlayerJoinedEvent notifies room that a player joined func (s *MatrixService) SendPlayerJoinedEvent(ctx context.Context, roomID string, sessionID string, playerID string, playerName string) error { event := GameEvent{ Type: GameEventPlayerJoined, SessionID: sessionID, PlayerID: playerID, Data: map[string]string{ "player_name": playerName, }, } // Also send a visible message msg := fmt.Sprintf("๐ฎ %s ist dem Spiel beigetreten!", playerName) if err := s.SendMessage(ctx, roomID, msg); err != nil { // Log but don't fail fmt.Printf("Warning: failed to send join message: %v\n", err) } return s.SendGameEvent(ctx, roomID, event) } // SendScoreUpdateEvent broadcasts score updates func (s *MatrixService) SendScoreUpdateEvent(ctx context.Context, roomID string, sessionID string, playerID string, score int, accuracy float64) error { event := GameEvent{ Type: GameEventScoreUpdate, SessionID: sessionID, PlayerID: playerID, Data: map[string]interface{}{ "score": score, "accuracy": accuracy, }, } return s.SendGameEvent(ctx, roomID, event) } // SendQuizAnsweredEvent broadcasts when a player answers a quiz func (s *MatrixService) SendQuizAnsweredEvent(ctx context.Context, roomID string, sessionID string, playerID string, correct bool, subject string) error { event := GameEvent{ Type: GameEventQuizAnswered, SessionID: sessionID, PlayerID: playerID, Data: map[string]interface{}{ "correct": correct, "subject": subject, }, } return s.SendGameEvent(ctx, roomID, event) } // SendAchievementEvent broadcasts when a player earns an achievement func (s *MatrixService) SendAchievementEvent(ctx context.Context, roomID string, sessionID string, playerID string, achievementID string, achievementName string) error { event := GameEvent{ Type: GameEventAchievement, SessionID: sessionID, PlayerID: playerID, Data: map[string]interface{}{ "achievement_id": achievementID, "achievement_name": achievementName, }, } // Also send a visible celebration message msg := fmt.Sprintf("๐ Erfolg freigeschaltet: %s!", achievementName) if err := s.SendMessage(ctx, roomID, msg); err != nil { fmt.Printf("Warning: failed to send achievement message: %v\n", err) } return s.SendGameEvent(ctx, roomID, event) } // SendChallengeWonEvent broadcasts challenge result func (s *MatrixService) SendChallengeWonEvent(ctx context.Context, roomID string, sessionID string, winnerID string, winnerName string, loserName string, winnerScore int, loserScore int) error { event := GameEvent{ Type: GameEventChallengeWon, SessionID: sessionID, PlayerID: winnerID, Data: map[string]interface{}{ "winner_name": winnerName, "loser_name": loserName, "winner_score": winnerScore, "loser_score": loserScore, }, } // Send celebration message msg := fmt.Sprintf("๐ %s gewinnt gegen %s mit %d zu %d Punkten!", winnerName, loserName, winnerScore, loserScore) if err := s.SendHTMLMessage(ctx, roomID, msg, fmt.Sprintf("
%s gewinnt gegen %s
Endstand: %d : %d
", winnerName, loserName, winnerScore, loserScore)); err != nil { fmt.Printf("Warning: failed to send challenge result message: %v\n", err) } return s.SendGameEvent(ctx, roomID, event) } // SendClassRaceLeaderboard broadcasts current leaderboard in class race func (s *MatrixService) SendClassRaceLeaderboard(ctx context.Context, roomID string, sessionID string, leaderboard []map[string]interface{}) error { // Build leaderboard message msg := "๐ Aktueller Stand:\n" htmlMsg := "