From cf20101be6a777a98dc8d20d4708385c3fb50c19 Mon Sep 17 00:00:00 2001 From: Illia Date: Fri, 5 Jun 2026 14:59:40 +0200 Subject: [PATCH] chore(ws): update game engine --- internal/domain/session.go | 66 ++++++++++++++++++++++++++-- internal/interfaces/ws/ws_handler.go | 63 ++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/internal/domain/session.go b/internal/domain/session.go index aa162a0..ff866fb 100644 --- a/internal/domain/session.go +++ b/internal/domain/session.go @@ -8,9 +8,10 @@ import ( type SessionStatus string const ( - StatusActive SessionStatus = "ACTIVE" - StatusMatched SessionStatus = "MATCHED" - StatusFinished SessionStatus = "FINISHED" + StatusActive SessionStatus = "ACTIVE" + StatusMatched SessionStatus = "MATCHED" + StatusFinished SessionStatus = "FINISHED" + StatusHostTieBreaker SessionStatus = "HOST_TIE_BREAKER" ) type VoteType string @@ -35,6 +36,7 @@ type Session struct { Pool []Restaurant `json:"pool"` MatchedID string `json:"matched_id,omitempty"` Votes map[string]map[string]VoteType `json:"votes"` + TiedIDs []string `json:"tied_ids,omitempty"` CreatedAt time.Time `json:"created_at"` } @@ -70,7 +72,15 @@ func (s *Session) RecordVote(userID, restaurantID string, vote VoteType) (bool, s.Votes[restaurantID][userID] = vote - return s.CheckConsensus(restaurantID), nil + if s.CheckConsensus(restaurantID) { + return true, nil + } + + if s.CheckCompletionFallback() { + return true, nil + } + + return false, nil } func (s *Session) CheckConsensus(restaurantID string) bool { @@ -93,3 +103,51 @@ func (s *Session) CheckConsensus(restaurantID string) bool { return false } + +func (s *Session) CheckCompletionFallback() bool { + totalExpectedVotes := len(s.Participants) * len(s.Pool) + totalCastVotes := 0 + + for _, userVotes := range s.Votes { + totalCastVotes += len(userVotes) + } + + if totalCastVotes < totalExpectedVotes || totalExpectedVotes == 0 { + return false + } + + bestScore := -9999 + var tiedIDs []string + + for restID, userVotes := range s.Votes { + score := 0 + for _, vote := range userVotes { + if vote == VoteLike { + score += 1 + } else if vote == VoteSuperLike { + score += 2 + } else if vote == VoteDislike { + score -= 1 + } + } + + if score > bestScore { + bestScore = score + tiedIDs = []string{restID} + } else if score == bestScore { + tiedIDs = append(tiedIDs, restID) + } + } + + if len(tiedIDs) == 1 { + s.Status = StatusMatched + s.MatchedID = tiedIDs[0] + return true + } else if len(tiedIDs) > 1 { + s.Status = StatusHostTieBreaker + s.TiedIDs = tiedIDs + return true + } + + return false +} diff --git a/internal/interfaces/ws/ws_handler.go b/internal/interfaces/ws/ws_handler.go index 392cf90..d5dc918 100644 --- a/internal/interfaces/ws/ws_handler.go +++ b/internal/interfaces/ws/ws_handler.go @@ -115,6 +115,69 @@ func (h *WSHandler) HandleConnection(w http.ResponseWriter, r *http.Request) { "session": updatedSession, "is_match": isMatch, }) + } else if msg.Action == "RESOLVE_TIE" { + session, err := h.sessionRepo.GetByID(context.Background(), sessionID) + if err != nil { + continue + } + + if session.HostID != msg.UserID { + log.Println("Unauthorized tie resolution attempt") + continue + } + + session.Status = domain.StatusMatched + session.MatchedID = msg.RestaurantID + + err = h.sessionRepo.Save(context.Background(), session) + if err != nil { + log.Printf("Failed to save resolved tie: %v", err) + continue + } + + h.hub.Broadcast(sessionID, map[string]interface{}{ + "event": "SESSION_UPDATED", + "session": session, + "is_match": true, + }) + } else if msg.Action == "START_SECOND_ROUND" { + session, err := h.sessionRepo.GetByID(context.Background(), sessionID) + if err != nil { + continue + } + + if session.HostID != msg.UserID { + log.Println("Unauthorized second round attempt") + continue + } + + var newPool []domain.Restaurant + for _, rest := range session.Pool { + for _, tiedID := range session.TiedIDs { + if rest.ID == tiedID { + newPool = append(newPool, rest) + break + } + } + } + + session.Pool = newPool + session.Votes = make(map[string]map[string]domain.VoteType) + session.Status = domain.StatusActive + session.TiedIDs = nil + session.MatchedID = "" + + err = h.sessionRepo.Save(context.Background(), session) + if err != nil { + log.Printf("Failed to start second round: %v", err) + continue + } + + h.hub.Broadcast(sessionID, map[string]interface{}{ + "event": "SESSION_UPDATED", + "session": session, + "is_match": false, + }) } } }