Skip to content

Commit

Permalink
idea: tune down recommendations difficulty based on lower top plays?
Browse files Browse the repository at this point in the history
  • Loading branch information
Tillerino committed Mar 27, 2023
1 parent c0b7a8d commit c30aac7
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@

import org.tillerino.osuApiModel.types.BitwiseMods;

import lombok.Getter;
import lombok.Builder;
import lombok.Getter;
import tillerino.tillerinobot.predicates.RecommendationPredicate;

@Builder
public record RecommendationRequest(
boolean nomod,
Model model,
@BitwiseMods long requestedMods,
List<RecommendationPredicate> predicates
List<RecommendationPredicate> predicates,
Shift difficultyShift
) {
public RecommendationRequest {
predicates = new ArrayList<>(predicates);
Expand All @@ -32,6 +33,8 @@ public static class RecommendationRequestBuilder {

private List<RecommendationPredicate> predicates = new ArrayList<>();

private Shift difficultyShift = Shift.NONE;

public RecommendationRequestBuilder requestedMods(@BitwiseMods long requestedMods) {
this.requestedMods = requestedMods;
return this;
Expand All @@ -51,4 +54,25 @@ public List<RecommendationPredicate> getPredicates() {
}
}

/**
* Modifies the difficulty of recommendations.
*/
static enum Shift {
/**
* Regular strength.
*/
NONE,
/**
* The player is weak compared to their top scores. Recommendations are easier.
*/
SUCC,
/**
* Even weaker than {@link #SUCC}
*/
SUCCER,
/**
* Even weaker than {@link #SUCCERBERG}
*/
SUCCERBERG
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import tillerino.tillerinobot.predicates.PredicateParser;
import tillerino.tillerinobot.predicates.RecommendationPredicate;
import tillerino.tillerinobot.recommendations.RecommendationRequest.RecommendationRequestBuilder;
import tillerino.tillerinobot.recommendations.RecommendationRequest.Shift;

@RequiredArgsConstructor(onConstructor = @__(@Inject))
public class RecommendationRequestParser {
Expand Down Expand Up @@ -49,7 +50,8 @@ public RecommendationRequest parseSamplerSettings(OsuApiUser apiUser, @Nonnull S
continue;
if (!parseEngines(param, settingsBuilder, apiUser)
&& !parseMods(param, settingsBuilder)
&& !parsePredicates(param, settingsBuilder, apiUser, lang)) {
&& !parsePredicates(param, settingsBuilder, apiUser, lang)
&& !parseOther(param, settingsBuilder, apiUser)) {
throw new UserException(lang.invalidChoice(param, STANDARD_SYNTAX));
}
}
Expand Down Expand Up @@ -157,4 +159,24 @@ private boolean parsePredicates(String param, RecommendationRequestBuilder setti
}
return false;
}

private boolean parseOther(String param, RecommendationRequestBuilder settingsBuilder, OsuApiUser apiUser)
throws SQLException, IOException {
String lowerCase = param.toLowerCase();
if (backend.getDonator(apiUser.getUserId()) > 0) {
switch (lowerCase) {
case "succ":
settingsBuilder.difficultyShift(Shift.SUCC);
return true;
case "succer":
settingsBuilder.difficultyShift(Shift.SUCCER);
return true;
}
if (getLevenshteinDistance(lowerCase, "succerberg") <= 2) {
settingsBuilder.difficultyShift(Shift.SUCCERBERG);
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package tillerino.tillerinobot.recommendations;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.tillerino.osuApiModel.Mods.Nightcore;
import static org.tillerino.osuApiModel.Mods.getEffectiveMods;
Expand Down Expand Up @@ -47,6 +48,7 @@
import tillerino.tillerinobot.data.util.ThreadLocalAutoCommittingEntityManager;
import tillerino.tillerinobot.lang.Language;
import tillerino.tillerinobot.predicates.RecommendationPredicate;
import tillerino.tillerinobot.recommendations.RecommendationRequest.Shift;

/**
* Communicates with the backend and creates recommendations samplers as well as caching information.
Expand Down Expand Up @@ -188,6 +190,16 @@ private Sampler<BareRecommendation, RecommendationRequest> loadSampler(@UserId i
exclude.add(play.getBeatmapid());
}

if (settings.difficultyShift() != Shift.NONE) {
int limit = switch (settings.difficultyShift()) {
case SUCC -> topPlays.size() / 2;
case SUCCER -> topPlays.size() / 4;
case SUCCERBERG -> 5;
default -> throw new IllegalStateException();
};
topPlays = topPlays.stream().sorted(Comparator.comparingDouble(TopPlay::getPp)).limit(limit).collect(toList());
}

Collection<BareRecommendation> recommendations = recommender.loadRecommendations(topPlays, exclude,
settings.model(), settings.nomod(), settings.requestedMods());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ public Collection<BareRecommendation> loadRecommendations(List<TopPlay> topPlays
}

double equivalentPp(List<TopPlay> plays) {
plays = new ArrayList<>(plays);
Collections.sort(plays, Comparator.comparingDouble(TopPlay::getPp).reversed());
double ppSum = 0;
double partialSum = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

import org.junit.Test;
Expand All @@ -20,6 +21,7 @@
import tillerino.tillerinobot.predicates.MapLength;
import tillerino.tillerinobot.predicates.NumericPropertyPredicate;
import tillerino.tillerinobot.predicates.StarDiff;
import tillerino.tillerinobot.recommendations.RecommendationRequest.Shift;

@RunWith(MockitoJUnitRunner.class)
public class RecommendationRequestParserTest {
Expand All @@ -35,6 +37,12 @@ private RecommendationRequest parse(String settings) throws Exception {
return recommendationRequestParser.parseSamplerSettings(user, settings, new Default());
}

@Test
public void defaultSettings() throws Exception {
RecommendationRequest request = parse("");
assertThat(request).returns(Shift.NONE, RecommendationRequest::difficultyShift);
}

@Test
public void testModContradiction() throws Exception {
when(backend.getDonator(anyInt())).thenReturn(1);
Expand Down Expand Up @@ -82,4 +90,15 @@ public void parseNap() throws Exception {
.hasFieldOrPropertyWithValue("model", Model.NAP)
.hasFieldOrPropertyWithValue("requestedMods", 64L);
}

@Test
public void testSucc() throws Exception {
assertThatThrownBy(() -> parse("succ")).isInstanceOf(UserException.class);

doReturn(1).when(backend).getDonator(1);
assertThat(parse("succ")).returns(Shift.SUCC, RecommendationRequest::difficultyShift);
assertThat(parse("succer")).returns(Shift.SUCCER, RecommendationRequest::difficultyShift);
// typo; long word
assertThat(parse("sucerberg")).returns(Shift.SUCCERBERG, RecommendationRequest::difficultyShift);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package tillerino.tillerinobot.recommendations;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import javax.inject.Inject;
Expand Down Expand Up @@ -153,4 +153,31 @@ public void gamma5NotRestricted() throws Exception {
user = backend.downloadUser("guy");
assertThat(manager.getRecommendation(user, "gamma5", new Default())).isNotNull();
}

@Test
public void succ() throws Exception {
runShift("succ", 25);
}

@Test
public void succer() throws Exception {
runShift("succer", 12);
}

@Test
public void succerberg() throws Exception {
runShift("succerberg", 5);
}

private void runShift(String mode, int limit) throws IOException, SQLException, UserException, InterruptedException {
backend.hintUser("guy", true, 123, 1000);
user = backend.downloadUser("guy");

List<TopPlay> topPlays = new ArrayList<>(recommender.loadTopPlays(user.getUserId()));
assertThat(topPlays).hasSize(50);
topPlays.sort(Comparator.comparingDouble(TopPlay::getPp));

assertThat(manager.getRecommendation(user, mode, new Default())).isNotNull();
verify(recommender).loadRecommendations(argThat(l -> l.equals(topPlays.subList(0, limit))), any(), eq(Model.GAMMA8), anyBoolean(), anyLong());
}
}

0 comments on commit c30aac7

Please sign in to comment.