diff --git a/domain/src/main/java/org/badminton/domain/common/error/ErrorCode.java b/domain/src/main/java/org/badminton/domain/common/error/ErrorCode.java index 7e57dcbe..b3f638fb 100644 --- a/domain/src/main/java/org/badminton/domain/common/error/ErrorCode.java +++ b/domain/src/main/java/org/badminton/domain/common/error/ErrorCode.java @@ -104,7 +104,7 @@ public enum ErrorCode { CLUB_OWNER_CANT_WITHDRAW(412, "동호회원이 2명 이상인 동호회 회장은 동호회를 탈퇴할 수 없습니다."), NOT_LEAGUE_OWNER(412, "경기를 만든 사용자만 수정 및 삭제를 할 수 있습니다"), CLUB_MEMBER_EXPEL_EXCEPTION(412, "해당 동호회에서 제제를 받아 가입 신청을 할 수 없습니다."), - BYE_MATCH(412, "해당 매치는 부전승 매치 입니다"), + BYE_MATCH_ACTION_NOT_ALLOWED(412, "해당 매치는 부전승 매치이기 때문에 점수를 수정할 수 없습니다"), // 500 Errors INTERNAL_SERVER_ERROR(500, "서버 내부 오류가 발생했습니다."), diff --git a/domain/src/main/java/org/badminton/domain/common/exception/match/ByeMatchException.java b/domain/src/main/java/org/badminton/domain/common/exception/match/ByeMatchException.java deleted file mode 100644 index 08fccdc9..00000000 --- a/domain/src/main/java/org/badminton/domain/common/exception/match/ByeMatchException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.badminton.domain.common.exception.match; - -import org.badminton.domain.common.error.ErrorCode; -import org.badminton.domain.common.exception.BadmintonException; - -public class ByeMatchException extends BadmintonException { - - public ByeMatchException(Long matchId) { - super(ErrorCode.BYE_MATCH, "[매치 아이디 : " + matchId + "]"); - } - - public ByeMatchException(Long matchId, Exception e) { - super(ErrorCode.BYE_MATCH, "[매치 아이디 : " + matchId + "]", e); - } -} diff --git a/domain/src/main/java/org/badminton/domain/common/exception/match/RegisterScoreInByeMatchException.java b/domain/src/main/java/org/badminton/domain/common/exception/match/RegisterScoreInByeMatchException.java new file mode 100644 index 00000000..93c316e8 --- /dev/null +++ b/domain/src/main/java/org/badminton/domain/common/exception/match/RegisterScoreInByeMatchException.java @@ -0,0 +1,16 @@ +package org.badminton.domain.common.exception.match; + +import org.badminton.domain.common.enums.MatchType; +import org.badminton.domain.common.error.ErrorCode; +import org.badminton.domain.common.exception.BadmintonException; + +public class RegisterScoreInByeMatchException extends BadmintonException { + + public RegisterScoreInByeMatchException(Long matchId, MatchType matchType) { + super(ErrorCode.BYE_MATCH_ACTION_NOT_ALLOWED, "[매치 아이디 : " + matchId + "매치 유형: " + matchType + "]"); + } + + public RegisterScoreInByeMatchException(Long matchId, MatchType matchType, Exception e) { + super(ErrorCode.BYE_MATCH_ACTION_NOT_ALLOWED, "[매치 아이디 : " + matchId + "매치 유형: " + matchType + "]", e); + } +} diff --git a/domain/src/main/java/org/badminton/domain/domain/match/entity/DoublesMatch.java b/domain/src/main/java/org/badminton/domain/domain/match/entity/DoublesMatch.java index bb419511..b6b9d6e0 100644 --- a/domain/src/main/java/org/badminton/domain/domain/match/entity/DoublesMatch.java +++ b/domain/src/main/java/org/badminton/domain/domain/match/entity/DoublesMatch.java @@ -133,4 +133,8 @@ public Integer getSetNumberInProgress() { } return getSetInProgress().get().getSetNumber(); } + + public void byeMatch() { + this.matchStatus = MatchStatus.BYE; + } } diff --git a/infrastructure/src/main/java/org/badminton/infrastructure/match/reader/DoublesMatchReaderImpl.java b/infrastructure/src/main/java/org/badminton/infrastructure/match/reader/DoublesMatchReaderImpl.java index 6ef8b51a..290de129 100644 --- a/infrastructure/src/main/java/org/badminton/infrastructure/match/reader/DoublesMatchReaderImpl.java +++ b/infrastructure/src/main/java/org/badminton/infrastructure/match/reader/DoublesMatchReaderImpl.java @@ -1,5 +1,6 @@ package org.badminton.infrastructure.match.reader; +import java.util.Arrays; import java.util.List; import org.badminton.domain.common.enums.MatchStatus; @@ -64,12 +65,13 @@ public boolean allMatchesFinishedForLeague(Long leagueId) { @Override public boolean allMatchesNotStartedForLeague(Long leagueId) { - return doublesMatchRepository.allMatchesNotStartedForLeague(leagueId); + List UnPlayedMatchStatuses = Arrays.asList(MatchStatus.NOT_STARTED, MatchStatus.BYE); + return doublesMatchRepository.allMatchesNotStartedForLeague(leagueId, UnPlayedMatchStatuses); } @Override public boolean allRoundMatchesDone(Long leagueId, int roundNumber) { - List statuses = List.of(MatchStatus.FINISHED, MatchStatus.BYE); - return doublesMatchRepository.areAllMatchesFinishedOrBye(leagueId, roundNumber, statuses); + List CompletedOrBypassedStatuses = List.of(MatchStatus.FINISHED, MatchStatus.BYE); + return doublesMatchRepository.areAllMatchesFinishedOrBye(leagueId, roundNumber, CompletedOrBypassedStatuses); } } diff --git a/infrastructure/src/main/java/org/badminton/infrastructure/match/repository/DoublesMatchRepository.java b/infrastructure/src/main/java/org/badminton/infrastructure/match/repository/DoublesMatchRepository.java index d562bf1b..ef093137 100644 --- a/infrastructure/src/main/java/org/badminton/infrastructure/match/repository/DoublesMatchRepository.java +++ b/infrastructure/src/main/java/org/badminton/infrastructure/match/repository/DoublesMatchRepository.java @@ -27,8 +27,9 @@ boolean areAllMatchesFinishedOrBye(@Param("leagueId") Long leagueId, @Param("rou @Query("SELECT COUNT(m) = 0 FROM DoublesMatch m WHERE m.league.leagueId = :leagueId AND m.matchStatus != 'FINISHED'") boolean allMatchesFinishedForLeague(@Param("leagueId") Long leagueId); - @Query("SELECT COUNT(m) = 0 FROM DoublesMatch m WHERE m.league.leagueId = :leagueId AND m.matchStatus != 'NOT_STARTED'") - boolean allMatchesNotStartedForLeague(@Param("leagueId") Long leagueId); + @Query("SELECT NOT EXISTS (SELECT 1 FROM DoublesMatch match WHERE match.league.leagueId = :leagueId AND match.matchStatus NOT IN (:statuses))") + boolean allMatchesNotStartedForLeague(@Param("leagueId") Long leagueId, + @Param("statuses") List statuses); } diff --git a/infrastructure/src/main/java/org/badminton/infrastructure/match/strategy/TournamentDoublesMatchStrategy.java b/infrastructure/src/main/java/org/badminton/infrastructure/match/strategy/TournamentDoublesMatchStrategy.java index c841f275..135af220 100644 --- a/infrastructure/src/main/java/org/badminton/infrastructure/match/strategy/TournamentDoublesMatchStrategy.java +++ b/infrastructure/src/main/java/org/badminton/infrastructure/match/strategy/TournamentDoublesMatchStrategy.java @@ -5,10 +5,13 @@ import java.util.List; import org.badminton.domain.common.enums.MatchResult; +import org.badminton.domain.common.enums.MatchStatus; +import org.badminton.domain.common.enums.MatchType; import org.badminton.domain.common.enums.SetStatus; import org.badminton.domain.common.exception.match.AlreadyWinnerDeterminedException; import org.badminton.domain.common.exception.match.LeagueParticipantNotDeterminedException; import org.badminton.domain.common.exception.match.PreviousDetNotFinishedException; +import org.badminton.domain.common.exception.match.RegisterScoreInByeMatchException; import org.badminton.domain.common.exception.match.RoundNotFinishedException; import org.badminton.domain.common.exception.match.SetFinishedException; import org.badminton.domain.domain.league.LeagueParticipantReader; @@ -57,6 +60,14 @@ private static boolean isMatchWinnerDetermined(DoublesMatch doublesMatch) { || doublesMatch.getTeam2MatchResult() == MatchResult.WIN; } + private static boolean isParticipantOddSize(List participants) { + return participants.size() % 4 != 0; + } + + private static boolean isDoubleMatchOddSize(List doublesMatches) { + return doublesMatches.size() % 2 != 0; + } + @Override public BracketInfo makeBracket(League league, List leagueParticipantList) { List allMatches = new ArrayList<>(); @@ -69,6 +80,10 @@ public BracketInfo makeBracket(League league, List leaguePart allMatches.addAll(createFirstRoundMatches(league, currentParticipants)); allMatches.addAll(createSubsequentRoundsMatches(league, totalRounds)); + allMatches.stream() + .filter(doublesMatch -> doublesMatch.getMatchStatus() == MatchStatus.BYE && doublesMatch.getTeam1() != null) + .forEach(this::updateNextRoundMatch); + return BracketInfo.fromDoubles(totalRounds, allMatches); } @@ -82,6 +97,10 @@ public SetInfo.Main registerSetScoreInMatch(Long matchId, Integer setNumber, validatePreviousRoundCompletion(doublesMatch.getLeague().getLeagueId(), doublesMatch.getRoundNumber()); + if (doublesMatch.getMatchStatus() == MatchStatus.BYE) { + throw new RegisterScoreInByeMatchException(doublesMatch.getId(), MatchType.DOUBLES); + } + if (isMatchWinnerDetermined(doublesMatch)) { throw new AlreadyWinnerDeterminedException(doublesMatch.getId()); } @@ -101,8 +120,7 @@ public SetInfo.Main registerSetScoreInMatch(Long matchId, Integer setNumber, } if (isMatchWinnerDetermined(doublesMatch)) { - doublesMatchStore.store(doublesMatch); - updateNextRoundMatch(doublesMatch); + processMatchAndNextRound(doublesMatch); } if (isAllMatchFinished(doublesMatch)) { leagueReader.readLeagueById(doublesMatch.getLeague().getLeagueId()).finishLeague(); @@ -155,9 +173,28 @@ public Main retrieveSet(Long matchId, int setNumber) { return SetInfo.fromDoublesSet(matchId, setNumber, doublesSet); } + private void processMatchAndNextRound(DoublesMatch doublesMatch) { + doublesMatchStore.store(doublesMatch); + updateNextRoundMatch(doublesMatch); + boolean allRoundMatchesDone = doublesMatchReader.allRoundMatchesDone(doublesMatch.getLeague().getLeagueId(), + doublesMatch.getRoundNumber()); + boolean currentRoundInTotalRound = + doublesMatch.getRoundNumber() < doublesMatch.getLeague().getTotalRounds(); + + if (allRoundMatchesDone && currentRoundInTotalRound) { + List nextRoundMatches = doublesMatchReader.findMatchesByLeagueAndRound( + doublesMatch.getLeague().getLeagueId(), doublesMatch.getRoundNumber() + 1 + ); + nextRoundMatches.stream() + .filter(match -> match.getMatchStatus() == MatchStatus.BYE) + .forEach(this::updateNextRoundMatch); + } + } + private List createFirstRoundMatches(League league, List participants) { List matches = new ArrayList<>(); - for (int i = 0; i < participants.size(); i += PARTICIPANTS_PER_TEAM * TEAMS_PER_MATCH) { + + for (int i = 0; i < participants.size() - 2; i += PARTICIPANTS_PER_TEAM * TEAMS_PER_MATCH) { Team team1 = new Team(participants.get(i), participants.get(i + 1)); Team team2 = new Team(participants.get(i + 2), participants.get(i + 3)); DoublesMatch match = new DoublesMatch(league, team1, team2, 1); @@ -165,18 +202,55 @@ private List createFirstRoundMatches(League league, List createRoundMatches(League league, List previousMatches, int roundNumber) { + private List createMatchesRound(League league, List previousRoundMatches, + int roundNumber) { List currentRoundMatches = new ArrayList<>(); - for (int i = 0; i < previousMatches.size(); i += TEAMS_PER_MATCH) { + + currentRoundMatches.addAll(createRegularMatchesForRound(league, previousRoundMatches, roundNumber)); + + if (isDoubleMatchOddSize(previousRoundMatches)) { + currentRoundMatches.add(createByeRoundMatch(league, previousRoundMatches, roundNumber)); + } + + return currentRoundMatches; + } + + private List createRegularMatchesForRound(League league, List previousRoundMatches, + int roundNumber) { + List regularRoundMatches = new ArrayList<>(); + + for (int i = 0; i < previousRoundMatches.size() - 1; i += TEAMS_PER_MATCH) { DoublesMatch match = new DoublesMatch(league, null, null, roundNumber); makeSetsInMatch(match); doublesMatchStore.store(match); - currentRoundMatches.add(match); + regularRoundMatches.add(match); } - return currentRoundMatches; + + return regularRoundMatches; + } + + private DoublesMatch createByeRoundMatch(League league, List previousRoundMatches, + int roundNumber) { + DoublesMatch byeMatch = previousRoundMatches.get(previousRoundMatches.size() - 1); + Team winner = determineWinner(byeMatch); + + DoublesMatch nextByeMatch = new DoublesMatch(league, winner, null, roundNumber); + nextByeMatch.byeMatch(); + doublesMatchStore.store(nextByeMatch); + + return nextByeMatch; } private List createSubsequentRoundsMatches(League league, int totalRounds) { @@ -184,7 +258,7 @@ private List createSubsequentRoundsMatches(League league, int tota List previousMatches = doublesMatchReader.findMatchesByLeagueAndRound(league.getLeagueId(), 1); for (int roundNumber = 2; roundNumber <= totalRounds; roundNumber++) { - List currentRoundMatches = createRoundMatches(league, previousMatches, roundNumber); + List currentRoundMatches = createMatchesRound(league, previousMatches, roundNumber); matches.addAll(currentRoundMatches); previousMatches = currentRoundMatches; } @@ -200,9 +274,9 @@ private void makeSetsInMatch(DoublesMatch doublesMatch) { doublesMatchStore.store(doublesMatch); } - private void updateSetScore(DoublesMatch doublesMatch, int setIndex, + private void updateSetScore(DoublesMatch doublesMatch, int setNumber, MatchCommand.UpdateSetScore updateSetScoreCommand) { - DoublesSet set = doublesMatch.getDoublesSet(setIndex); + DoublesSet set = doublesMatch.getDoublesSet(setNumber); set.endSetScore(updateSetScoreCommand.getScore1(), updateSetScoreCommand.getScore2()); if (updateSetScoreCommand.getScore1() > updateSetScoreCommand.getScore2()) { @@ -231,25 +305,37 @@ private void updateNextRoundMatch(DoublesMatch doublesMatch) { Math.toIntExact(startMatch.getId())); DoublesMatch nextRoundMatch = doublesMatchReader.getDoublesMatch((long)nextRoundMatchId); - if (nextRoundMatch != null) { - assignWinnerToNextRoundMatch(nextRoundMatch, winner); + + if (nextRoundMatch == null) { + return; + } + + if (doublesMatch.getMatchStatus() == MatchStatus.BYE) { + nextRoundMatch.defineTeam1(winner); doublesMatchStore.store(nextRoundMatch); + return; } + assignWinnerToNextRoundMatch(nextRoundMatch, winner); + doublesMatchStore.store(nextRoundMatch); } private void assignWinnerToNextRoundMatch(DoublesMatch nextRoundMatch, Team winner) { if (nextRoundMatch.getTeam1() == null) { nextRoundMatch.defineTeam1(winner); - } else { + return; + } + if (nextRoundMatch.getTeam2() == null) { nextRoundMatch.defineTeam2(winner); } + } private Team determineWinner(DoublesMatch match) { - if (match.getTeam1MatchResult() == MatchResult.WIN) { + if (match.getMatchStatus() == MatchStatus.BYE || match.getTeam1MatchResult() == MatchResult.WIN) { return match.getTeam1(); } + if (match.getTeam2MatchResult() == MatchResult.WIN) { return match.getTeam2(); } diff --git a/infrastructure/src/main/java/org/badminton/infrastructure/match/strategy/TournamentSinglesMatchStrategy.java b/infrastructure/src/main/java/org/badminton/infrastructure/match/strategy/TournamentSinglesMatchStrategy.java index 765031d2..ff996db6 100644 --- a/infrastructure/src/main/java/org/badminton/infrastructure/match/strategy/TournamentSinglesMatchStrategy.java +++ b/infrastructure/src/main/java/org/badminton/infrastructure/match/strategy/TournamentSinglesMatchStrategy.java @@ -6,11 +6,12 @@ import org.badminton.domain.common.enums.MatchResult; import org.badminton.domain.common.enums.MatchStatus; +import org.badminton.domain.common.enums.MatchType; import org.badminton.domain.common.enums.SetStatus; import org.badminton.domain.common.exception.match.AlreadyWinnerDeterminedException; -import org.badminton.domain.common.exception.match.ByeMatchException; import org.badminton.domain.common.exception.match.LeagueParticipantNotDeterminedException; import org.badminton.domain.common.exception.match.PreviousDetNotFinishedException; +import org.badminton.domain.common.exception.match.RegisterScoreInByeMatchException; import org.badminton.domain.common.exception.match.RoundNotFinishedException; import org.badminton.domain.common.exception.match.SetFinishedException; import org.badminton.domain.domain.league.LeagueParticipantReader; @@ -95,7 +96,7 @@ public SetInfo.Main registerSetScoreInMatch(Long matchId, Integer setNumber, validatePreviousRoundCompletion(singlesMatch.getLeague().getLeagueId(), singlesMatch.getRoundNumber()); if (singlesMatch.getMatchStatus() == MatchStatus.BYE) { - throw new ByeMatchException(singlesMatch.getId()); + throw new RegisterScoreInByeMatchException(singlesMatch.getId(), MatchType.SINGLES); } if (isMatchWinnerDetermined(singlesMatch)) {