Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.codzilla.backend.PreMatch.DTO;

public record MatchResultDTO(String outcome, int newRating, int ratingDelta) {}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public class WebSocketDTO {
public enum Status {
MATCH_STARTED_REDIRECT,
DRAFT,
LIVE
LIVE,
MATCH_FINISHED
}

public WebSocketDTO(Status status, Object payload) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.codzilla.backend.PreMatch;

import com.codzilla.backend.PreMatch.DTO.DraftSessionResponseDTO;
import com.codzilla.backend.PreMatch.DTO.MatchResultDTO;
import com.codzilla.backend.PreMatch.DTO.WebSocketDTO;
import com.codzilla.backend.PreMatch.MatchRoom.Match;
import com.codzilla.backend.PreMatch.events.MatchResultNotifyEvent;
import com.codzilla.backend.PreMatch.MatchRoom.MatchService;
import com.codzilla.backend.PreMatch.events.DraftSessionFinishedEvent;
import com.codzilla.backend.PreMatch.events.DraftSessionUpdatedEvent;
Expand Down Expand Up @@ -51,4 +52,26 @@ public void handleDraftSessionFinished(DraftSessionFinishedEvent event) {
)
);
}

@Async
@EventListener
public void handleMatchResult(MatchResultNotifyEvent event) {
messagingTemplate.convertAndSendToUser(
event.winnerEmail(),
"/queue/match-result",
new WebSocketDTO(
WebSocketDTO.Status.MATCH_FINISHED,
new MatchResultDTO("WIN", event.winnerNewRating(), event.winnerRatingDelta())
)
);

messagingTemplate.convertAndSendToUser(
event.loserEmail(),
"/queue/match-result",
new WebSocketDTO(
WebSocketDTO.Status.MATCH_FINISHED,
new MatchResultDTO("LOSE", event.loserNewRating(), event.loserRatingDelta())
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codzilla.backend.PreMatch.events;

import java.util.UUID;

public record MatchResultNotifyEvent(
UUID matchId,
UUID winnerId,
UUID loserId,
String winnerEmail,
String loserEmail,
int winnerNewRating,
int winnerRatingDelta,
int loserNewRating,
int loserRatingDelta
) {}
22 changes: 20 additions & 2 deletions src/main/java/com/codzilla/backend/Rating/RatingService.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.codzilla.backend.Rating;

import com.codzilla.backend.PreMatch.events.MatchResultNotifyEvent;
import com.codzilla.backend.User.User;
import com.codzilla.backend.User.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -18,22 +20,26 @@
public class RatingService {

private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;
private final Glicko2 glicko2 = new Glicko2();

@EventListener
@Transactional
public void onMatchFinished(MatchFinishedEvent event) {
recalculate(event.winnerId(), event.loserId());
recalculate(event.matchId(), event.winnerId(), event.loserId());
}

public void recalculate(UUID winnerId, UUID loserId) {
public void recalculate(UUID matchId, UUID winnerId, UUID loserId) {
User winner = userRepository.findById(winnerId).orElse(null);
User loser = userRepository.findById(loserId).orElse(null);
if (winner == null || loser == null) {
log.warn("Skip rating update, user not found: winner={}, loser={}", winnerId, loserId);
return;
}

int winnerOldRating = winner.getRating();
int loserOldRating = loser.getRating();

Glicko2.Opponent loserAsOpponentOfWinner =
new Glicko2.Opponent(loser.getRating().doubleValue(), loser.getRatingDeviation(), 1.0);
Glicko2.Opponent winnerAsOpponentOfLoser =
Expand All @@ -55,6 +61,18 @@ public void recalculate(UUID winnerId, UUID loserId) {

log.info("Rating updated: winner {} -> {}, loser {} -> {}",
winnerId, winner.getRating(), loserId, loser.getRating());

eventPublisher.publishEvent(new MatchResultNotifyEvent(
matchId,
winnerId,
loserId,
winner.getEmail(),
loser.getEmail(),
winner.getRating(),
Math.abs(winner.getRating() - winnerOldRating),
loser.getRating(),
Math.abs(loser.getRating() - loserOldRating)
));
}

private void applyResult(User user, Glicko2.Result r, Instant now) {
Expand Down
40 changes: 38 additions & 2 deletions src/test/java/com/codzilla/backend/Rating/RatingServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.codzilla.backend.Rating;

import com.codzilla.backend.PreMatch.events.MatchResultNotifyEvent;
import com.codzilla.backend.User.User;
import com.codzilla.backend.User.UserRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationEventPublisher;

import java.util.Optional;
import java.util.UUID;
Expand All @@ -19,6 +22,9 @@ class RatingServiceTest {
@Mock
private UserRepository userRepository;

@Mock
private ApplicationEventPublisher eventPublisher;

private User newUser(UUID id, int rating) {
User u = User.builder()
.email(id + "@test.com")
Expand All @@ -32,7 +38,7 @@ private User newUser(UUID id, int rating) {

@Test
void matchFinishedEvent_winnerGains_loserLoses() {
RatingService service = new RatingService(userRepository);
RatingService service = new RatingService(userRepository, eventPublisher);

UUID winnerId = UUID.randomUUID();
UUID loserId = UUID.randomUUID();
Expand All @@ -59,9 +65,38 @@ void matchFinishedEvent_winnerGains_loserLoses() {
verify(userRepository).save(loser);
}

@Test
void matchFinishedEvent_publishesNotifyEventWithEmailsAndDeltas() {
RatingService service = new RatingService(userRepository, eventPublisher);

UUID winnerId = UUID.randomUUID();
UUID loserId = UUID.randomUUID();
User winner = newUser(winnerId, 1500);
User loser = newUser(loserId, 1500);

when(userRepository.findById(winnerId)).thenReturn(Optional.of(winner));
when(userRepository.findById(loserId)).thenReturn(Optional.of(loser));

service.onMatchFinished(new MatchFinishedEvent(UUID.randomUUID(), winnerId, loserId));

ArgumentCaptor<MatchResultNotifyEvent> captor =
ArgumentCaptor.forClass(MatchResultNotifyEvent.class);
verify(eventPublisher).publishEvent(captor.capture());
MatchResultNotifyEvent published = captor.getValue();

assertThat(published.winnerEmail()).isEqualTo(winner.getEmail());
assertThat(published.loserEmail()).isEqualTo(loser.getEmail());

assertThat(published.winnerNewRating()).isEqualTo(winner.getRating());
assertThat(published.loserNewRating()).isEqualTo(loser.getRating());

assertThat(published.winnerRatingDelta()).isEqualTo(162);
assertThat(published.loserRatingDelta()).isEqualTo(162);
}

@Test
void unknownUser_skipsWithoutSaving() {
RatingService service = new RatingService(userRepository);
RatingService service = new RatingService(userRepository, eventPublisher);
UUID winnerId = UUID.randomUUID();
UUID loserId = UUID.randomUUID();

Expand All @@ -71,5 +106,6 @@ void unknownUser_skipsWithoutSaving() {
service.onMatchFinished(new MatchFinishedEvent(UUID.randomUUID(), winnerId, loserId));

verify(userRepository, never()).save(any());
verify(eventPublisher, never()).publishEvent(any());
}
}
Loading