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
Expand Up @@ -43,7 +43,6 @@ public class PrivateGameMatchController {
private final GameMatchService gameMatchService;

@PostMapping
@RequireTurnstile
public ResponseEntity<List<GameMatchDto>> createGameMatch(
Comment on lines 43 to 46
@AuthenticationPrincipal User user, @RequestBody CreateMatchDto createMatchDto) {
Player player =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;

import org.bytefight.webserver.competition.domain.Competition;
import org.bytefight.webserver.gamematch.application.GameMatchService;
import org.bytefight.webserver.gamematch.domain.GameMatch;
import org.bytefight.webserver.gamematch.domain.MatchReason;
Expand Down Expand Up @@ -47,8 +48,7 @@
@RequiredArgsConstructor
public class TournamentMatchScheduler {
private static final String MAP_SETTING_KEY = "map";
private static final List<String> TOURNAMENT_SERIES_MAPS =
List.of("butterfly", "pumpkin", "ghost", "catzilla", "pickaxe", "shuriken", "squid");
private static final String TOURNAMENT_MAPS_SETTING_KEY = "tournamentMaps";

private final TournamentMatchRepository tournamentMatchRepository;
private final TournamentGameRepository tournamentGameRepository;
Expand Down Expand Up @@ -233,7 +233,8 @@ public void queueSeriesGame(TournamentMatch match) {

// Determine the next game number (1-based).
int nextGameNumber = existingGames.size() + 1;
Map<String, Object> matchSettings = buildSeriesMatchSettings(existingGames);
List<String> tournamentMaps = getTournamentMaps(match.getTournament().getCompetition());
Map<String, Object> matchSettings = buildSeriesMatchSettings(existingGames, tournamentMaps);
Comment on lines 233 to +237

// Create the underlying GameMatch and push it to the queue.
GameMatch gameMatch =
Expand Down Expand Up @@ -263,11 +264,31 @@ public void queueSeriesGame(TournamentMatch match) {
tournamentMatchRepository.save(match);
}

/**
* Reads {@code tournamentMaps} from the competition's settings. Throws if the key is absent or
* not a List, since map selection cannot proceed without it.
*/
@SuppressWarnings("unchecked")
private List<String> getTournamentMaps(Competition competition) {
Map<String, Object> settings = competition.getSettings();
Object value = settings == null ? null : settings.get(TOURNAMENT_MAPS_SETTING_KEY);
if (!(value instanceof List)) {
throw new IllegalStateException(
"Competition '"
+ competition.getSlug()
+ "' is missing the required '"
+ TOURNAMENT_MAPS_SETTING_KEY
+ "' setting.");
}
return (List<String>) value;
}
Comment on lines +271 to +284

/**
* Ensures map uniqueness inside a single best-of series. Chooses randomly from the remaining
* unused maps. Once all known maps have been used, returns null so the engine can auto-select.
*/
private Map<String, Object> buildSeriesMatchSettings(List<TournamentGame> existingGames) {
private Map<String, Object> buildSeriesMatchSettings(
List<TournamentGame> existingGames, List<String> tournamentMaps) {
Set<String> usedMaps = new HashSet<>();
for (TournamentGame game : existingGames) {
Map<String, Object> settings = game.getGameMatch().getMatchSettings();
Expand All @@ -281,7 +302,7 @@ private Map<String, Object> buildSeriesMatchSettings(List<TournamentGame> existi
}

List<String> availableMaps = new ArrayList<>();
for (String mapName : TOURNAMENT_SERIES_MAPS) {
for (String mapName : tournamentMaps) {
if (!usedMaps.contains(mapName)) {
availableMaps.add(mapName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.transaction.Transactional;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.bytefight.webserver.FullStackIntegrationTestBase;
Expand All @@ -24,6 +30,7 @@
import org.bytefight.webserver.tournament.domain.Tournament;
import org.bytefight.webserver.tournament.domain.TournamentBracketType;
import org.bytefight.webserver.tournament.domain.TournamentEntry;
import org.bytefight.webserver.tournament.domain.TournamentGame;
import org.bytefight.webserver.tournament.domain.TournamentMatch;
import org.bytefight.webserver.tournament.domain.TournamentMatchState;
import org.bytefight.webserver.tournament.domain.TournamentStatus;
Expand Down Expand Up @@ -147,72 +154,62 @@ void processTournamentAfterManualCompletionAdvancesBracket() {
assertEquals(2, queued.size());
}

// the test has incorrect map data which is why I commented it out for now
// @Test
// @Transactional
// void queueSeriesGameUsesUniqueMapsThenFallsBackToEngineChoice() {
// Competition competition = createCompetition("comp-scheduler-maps", true);
// Tournament tournament = createTournament(competition);
// Team teamOne = createTeamWithSubmission(competition, "Team One");
// Team teamTwo = createTeamWithSubmission(competition, "Team Two");

// TournamentEntry entryOne = tournamentEntryRepository.save(createEntry(tournament, teamOne,
// 1));
// TournamentEntry entryTwo = tournamentEntryRepository.save(createEntry(tournament, teamTwo,
// 2));

// TournamentMatch match = TournamentMatch.builder()
// .tournament(tournament)
// .bracketType(TournamentBracketType.GRAND_FINAL)
// .roundNumber(1)
// .matchIndex(1)
// .teamOneEntry(entryOne)
// .teamTwoEntry(entryTwo)
// .state(TournamentMatchState.PENDING)
// .seriesLength(TournamentBracketBuilder.GRAND_FINAL_SERIES_LENGTH)
// .teamOneSeriesWins(0)
// .teamTwoSeriesWins(0)
// .build();
// match = tournamentMatchRepository.save(match);

// for (int i = 0; i < 8; i++) {
// tournamentMatchScheduler.queueSeriesGame(match);
// }

// TournamentMatch refreshed =
// tournamentMatchRepository.findById(match.getId()).orElseThrow();
// List<TournamentGame> games =
// tournamentGameRepository.findByTournamentMatchOrderByGameNumberAsc(refreshed);
// assertEquals(8, games.size());

// List<String> expectedMaps = List.of(
// "the temple",
// "the complex",
// "matrix",
// "maze",
// "spiral",
// "disjoint",
// "big spiral"
// );

// Set<String> expectedMapSet = Set.copyOf(expectedMaps);
// Set<String> usedMapsInSeries = new HashSet<>();
// for (int i = 0; i < expectedMaps.size(); i++) {
// Object mapNameValue = games.get(i).getGameMatch().getMatchSettings().get("map");
// assertTrue(mapNameValue instanceof String, "Map should be present for the first seven
// games.");
// String mapName = (String) mapNameValue;
// assertTrue(expectedMapSet.contains(mapName), "Map should come from the tournament map
// pool.");
// assertTrue(usedMapsInSeries.add(mapName), "Map should be unique within the series.");
// }
// assertEquals(expectedMaps.size(), usedMapsInSeries.size(), "All unique maps should be
// consumed first.");

// assertTrue(games.get(7).getGameMatch().getMatchSettings().isEmpty(),
// "After all unique maps are used, match settings should be empty so engine can
// choose.");
// }
@Test
@Transactional
void queueSeriesGameUsesUniqueMapsThenFallsBackToEngineChoice() {
Competition competition = createCompetition("comp-scheduler-maps", true);
Tournament tournament = createTournament(competition);
Team teamOne = createTeamWithSubmission(competition, "Team One");
Team teamTwo = createTeamWithSubmission(competition, "Team Two");

TournamentEntry entryOne = tournamentEntryRepository.save(createEntry(tournament, teamOne, 1));
TournamentEntry entryTwo = tournamentEntryRepository.save(createEntry(tournament, teamTwo, 2));

TournamentMatch match =
TournamentMatch.builder()
.tournament(tournament)
.bracketType(TournamentBracketType.GRAND_FINAL)
.roundNumber(1)
.matchIndex(1)
.teamOneEntry(entryOne)
.teamTwoEntry(entryTwo)
.state(TournamentMatchState.PENDING)
.seriesLength(TournamentBracketBuilder.GRAND_FINAL_SERIES_LENGTH)
.teamOneSeriesWins(0)
.teamTwoSeriesWins(0)
.build();
match = tournamentMatchRepository.save(match);

for (int i = 0; i < 8; i++) {
tournamentMatchScheduler.queueSeriesGame(match);
}

TournamentMatch refreshed = tournamentMatchRepository.findById(match.getId()).orElseThrow();
List<TournamentGame> games =
tournamentGameRepository.findByTournamentMatchOrderByGameNumberAsc(refreshed);
assertEquals(8, games.size());

// These must match the tournamentMaps set on the competition in createCompetition().
List<String> expectedMaps =
List.of("butterfly", "pumpkin", "ghost", "catzilla", "pickaxe", "shuriken", "squid");

Set<String> expectedMapSet = Set.copyOf(expectedMaps);
Set<String> usedMapsInSeries = new HashSet<>();
for (int i = 0; i < expectedMaps.size(); i++) {
Object mapNameValue = games.get(i).getGameMatch().getMatchSettings().get("map");
assertTrue(mapNameValue instanceof String, "Map should be present for the first seven games.");
String mapName = (String) mapNameValue;
assertTrue(
expectedMapSet.contains(mapName), "Map should come from the tournament map pool.");
assertTrue(usedMapsInSeries.add(mapName), "Map should be unique within the series.");
}
assertEquals(
expectedMaps.size(), usedMapsInSeries.size(), "All unique maps should be consumed first.");

assertTrue(
games.get(7).getGameMatch().getMatchSettings().isEmpty(),
"After all unique maps are used, match settings should be empty so engine can choose.");
}

private void createSixSeededEntries(Tournament tournament, Competition competition) {
Team team1 = createTeamWithSubmission(competition, "Seed 1");
Expand Down Expand Up @@ -269,6 +266,10 @@ private Competition createCompetition(String slug, boolean active) {
competition.setActive(active);
competition.setWhitelisted(false);
competition.setMaxPlayersPerTeam(2);
competition.setSettings(
Map.of(
"tournamentMaps",
List.of("butterfly", "pumpkin", "ghost", "catzilla", "pickaxe", "shuriken", "squid")));
Competition saved = competitionRepository.save(competition);
ensureTournamentLadder(saved);
return saved;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.bytefight.webserver.FullStackIntegrationTestBase;
Expand Down Expand Up @@ -597,6 +598,10 @@ private Competition createCompetition(String slug, boolean active) {
competition.setActive(active);
competition.setWhitelisted(false);
competition.setMaxPlayersPerTeam(2);
competition.setSettings(
Map.of(
"tournamentMaps",
List.of("butterfly", "pumpkin", "ghost", "catzilla", "pickaxe", "shuriken", "squid")));
Competition saved = competitionRepository.save(competition);
ensureTournamentLadder(saved);
return saved;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.bytefight.webserver.FullStackIntegrationTestBase;
Expand Down Expand Up @@ -339,6 +340,10 @@ private Competition createCompetition(String slug, boolean active) {
competition.setActive(active);
competition.setWhitelisted(false);
competition.setMaxPlayersPerTeam(2);
competition.setSettings(
Map.of(
"tournamentMaps",
List.of("butterfly", "pumpkin", "ghost", "catzilla", "pickaxe", "shuriken", "squid")));
Competition saved = competitionRepository.save(competition);
ensureTournamentLadder(saved);
return saved;
Expand Down