This commit is contained in:
@@ -25,6 +25,13 @@ public class AchievementServiceImpl implements AchievementService {
|
||||
CATALOG.put("Dauerbrenner", "Spiele dasselbe Spiel zehnmal.");
|
||||
CATALOG.put("Punktesammler", "Sammle insgesamt 5000 Punkte.");
|
||||
CATALOG.put("Allrounder", "Spiele vier verschiedene Spiele.");
|
||||
CATALOG.put("Gedächtnis-Impuls", "Schließe eine MindMatch-Runde mit Punkten ab.");
|
||||
CATALOG.put("Quiz-Kapitän", "Schließe eine TechTacular-Runde mit Punkten ab.");
|
||||
CATALOG.put("Werkbank aktiv", "Schließe eine DevLab-Runde mit Punkten ab.");
|
||||
CATALOG.put("Syntax-Spur", "Schließe eine SyntaxSaga-Runde mit Punkten ab.");
|
||||
CATALOG.put("Fluchtprotokoll", "Schließe eine SystemEscape-Runde mit Punkten ab.");
|
||||
CATALOG.put("Erste Fallakte", "Schließe eine BugHunter-Akte mit Punkten ab.");
|
||||
CATALOG.put("Netzwerk-Neustart", "Schließe eine NetWorker-Welt mit Punkten ab.");
|
||||
}
|
||||
|
||||
private final AchievementRepository achievementRepository;
|
||||
@@ -70,6 +77,11 @@ public class AchievementServiceImpl implements AchievementService {
|
||||
gameScoreRepository.sumScoreByUsername(username) >= 5000);
|
||||
award(existing, username, "Allrounder",
|
||||
gameScoreRepository.countDistinctGamesByUsername(username) >= 4);
|
||||
String gameAchievement = Map.of(
|
||||
"mindmatch", "Gedächtnis-Impuls", "techtacular", "Quiz-Kapitän", "devlab", "Werkbank aktiv",
|
||||
"syntax-saga", "Syntax-Spur", "codeescape", "Fluchtprotokoll", "bughunter", "Erste Fallakte",
|
||||
"networker", "Netzwerk-Neustart").get(game);
|
||||
if (gameAchievement != null) award(existing, username, gameAchievement, score > 0);
|
||||
}
|
||||
|
||||
@Override @Transactional
|
||||
|
||||
@@ -25,12 +25,13 @@ public class BugHunterService {
|
||||
private final BugHunterBugRepository bugs;
|
||||
private final BugHunterHuntProgressRepository progress;
|
||||
private final GameScoreService scores;
|
||||
private final AchievementService achievements;
|
||||
private final ObjectMapper mapper;
|
||||
private final Random random = new Random();
|
||||
|
||||
public BugHunterService(BugHunterHuntRepository hunts, BugHunterFileRepository files, BugHunterBugRepository bugs,
|
||||
BugHunterHuntProgressRepository progress, GameScoreService scores, ObjectMapper mapper) {
|
||||
this.hunts = hunts; this.files = files; this.bugs = bugs; this.progress = progress; this.scores = scores; this.mapper = mapper;
|
||||
BugHunterHuntProgressRepository progress, GameScoreService scores, AchievementService achievements, ObjectMapper mapper) {
|
||||
this.hunts = hunts; this.files = files; this.bugs = bugs; this.progress = progress; this.scores = scores; this.achievements = achievements; this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@@ -129,7 +130,7 @@ public class BugHunterService {
|
||||
} else combo = 0;
|
||||
}
|
||||
boolean cleanSolve = solved == hunt.getBugs().size() && maxCombo == hunt.getBugs().size();
|
||||
if (cleanSolve && !blank(username)) markCleanSolved(username, hunt.getSlug());
|
||||
if (cleanSolve && !blank(username)) { markCleanSolved(username, hunt.getSlug()); achievements.awardSpecial(username, "Fallakten-Perfektionist", "Schließe eine BugHunter-Akte ohne Fehlversuch und ohne Auflösung ab."); }
|
||||
int stars = stars(base, max); ScoreResult saved = scores.recordScore(username, GAME, hunt.getSlug(), base + comboBonus, solved, request.getDurationSeconds());
|
||||
httpSession.removeAttribute("bughunter.run");
|
||||
return new BugHunterResult(base + comboBonus, base, max, comboBonus, maxCombo, solved, hunt.getBugs().size(), stars, saved.getHighscore(), saved.isNewHighscore(), saved.getPlays(), cleanSolve);
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.example.mindflux.service;
|
||||
import com.example.mindflux.dto.*; import com.example.mindflux.model.*; import com.example.mindflux.repository.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.node.*; import jakarta.servlet.http.HttpSession; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*;
|
||||
@Service public class CodeEscapeService {
|
||||
public static final String GAME="codeescape"; private static final Set<String>TYPES=Set.of("code","choice","order","match"),SCOPES=Set.of("AP1","AP2_FIAE","AP2_FISI","CROSS"); private static final int MAX_ITEMS=40;
|
||||
private final EscapeScenarioRepository scenarios;private final EscapeRoomRepository rooms;private final EscapePuzzleRepository puzzles;private final GameScoreService scores;private final ObjectMapper mapper;
|
||||
public CodeEscapeService(EscapeScenarioRepository s,EscapeRoomRepository r,EscapePuzzleRepository p,GameScoreService g,ObjectMapper m){scenarios=s;rooms=r;puzzles=p;scores=g;mapper=m;}
|
||||
private final EscapeScenarioRepository scenarios;private final EscapeRoomRepository rooms;private final EscapePuzzleRepository puzzles;private final GameScoreService scores;private final AchievementService achievements;private final ObjectMapper mapper;
|
||||
public CodeEscapeService(EscapeScenarioRepository s,EscapeRoomRepository r,EscapePuzzleRepository p,GameScoreService g,AchievementService a,ObjectMapper m){scenarios=s;rooms=r;puzzles=p;scores=g;achievements=a;mapper=m;}
|
||||
public static class RunState implements java.io.Serializable {public Long scenarioId;public String slug;public final List<Long>roomIds=new ArrayList<>();public final Set<Long>puzzleIds=new HashSet<>();public final Map<Long,Integer> attempts=new HashMap<>(),hints=new HashMap<>();public final Set<Long> revealed=new HashSet<>(),resolved=new HashSet<>(),opened=new LinkedHashSet<>();}
|
||||
private RunState state(HttpSession session){Object o=session.getAttribute("codeescape.run");if(o instanceof RunState s&&s.scenarioId!=null)return s;throw new IllegalArgumentException("Kein aktiver SystemEscape-Run.");}
|
||||
@Transactional(readOnly=true) public JsonNode catalog(){ArrayNode a=mapper.createArrayNode();for(EscapeScenario s:scenarios.findAllByActiveTrueOrderBySortOrderAscIdAsc()){if(s.getRooms().isEmpty())continue;ObjectNode n=a.addObject();n.put("slug",s.getSlug());n.put("title",s.getTitle());n.put("difficulty",s.getDifficulty());n.put("examScope",s.getExamScope());n.put("description",s.getDescription());n.put("timeLimitSeconds",s.getTimeLimitSeconds());n.put("roomCount",Math.min(4,s.getRooms().size()));n.put("poolSize",s.getRooms().size());}return mapper.createObjectNode().set("scenarios",a);}
|
||||
@@ -13,7 +13,7 @@ import com.example.mindflux.dto.*; import com.example.mindflux.model.*; import c
|
||||
@Transactional(readOnly=true) public JsonNode hint(Long id,int level,HttpSession session){RunState st=state(session);if(!st.puzzleIds.contains(id))throw new IllegalArgumentException("Rätsel gehört nicht zu diesem Run.");EscapePuzzle p=puzzles.findById(id).orElseThrow(()->new IllegalArgumentException("Rätsel nicht gefunden."));int lv=Math.max(1,Math.min(2,level));st.hints.merge(id,lv,Math::max);List<String>h=strings(parse(p.getPayload()),"hints");ObjectNode o=mapper.createObjectNode();o.put("level",lv);o.put("text",h.isEmpty()?"Prüfe die Aufgabenstellung noch einmal.":h.get(Math.min(lv-1,h.size()-1)));return o;}
|
||||
@Transactional(readOnly=true) public JsonNode door(Long id,String code,HttpSession session){RunState st=state(session);int next=st.opened.size();if(next>=st.roomIds.size()||!Objects.equals(st.roomIds.get(next),id))throw new IllegalArgumentException("Raum gehört nicht zum aktuellen Abschnitt dieses Runs.");EscapeRoom r=rooms.findById(id).orElseThrow(()->new IllegalArgumentException("Raum nicht gefunden."));boolean complete=r.getPuzzles().stream().allMatch(p->st.resolved.contains(p.getId()));boolean open=complete&&eq(r.getDoorCode(),code,false);if(open)st.opened.add(id);ObjectNode o=mapper.createObjectNode();o.put("open",open);o.put("complete",complete);if(open)o.put("successText",r.getSuccessText());else o.put("message",complete?"Code abgelehnt. Setze die Fragmente von links nach rechts ohne Leerzeichen zusammen.":"Löse zuerst alle Rätsel dieses Raums.");return o;}
|
||||
@Transactional public EscapeResult finish(String user,EscapeFinishRequest req,HttpSession session){RunState st=state(session);EscapeScenario s=scenarios.findBySlug(req.getSlug()).orElseThrow(()->new IllegalArgumentException("Szenario nicht gefunden."));if(!Objects.equals(st.scenarioId,s.getId())||!Objects.equals(st.slug,req.getSlug()))throw new IllegalArgumentException("Szenario gehört nicht zu diesem Run.");if(req.getDurationSeconds()<1||req.getDurationSeconds()>s.getTimeLimitSeconds()*4)throw new IllegalArgumentException("Unplausible Spieldauer.");if(req.getItems()==null||req.getItems().size()>MAX_ITEMS)throw new IllegalArgumentException("Ungültige Rätselliste.");Map<Long,EscapePuzzle>byId=new HashMap<>();Map<Long,EscapeRoom>selectedRooms=new HashMap<>();for(EscapeRoom r:s.getRooms())if(st.roomIds.contains(r.getId())){selectedRooms.put(r.getId(),r);for(EscapePuzzle p:r.getPuzzles())byId.put(p.getId(),p);}Set<Long>seen=new HashSet<>();int base=0,solved=0;for(EscapeFinishRequest.Item i:req.getItems()){if(i.getPuzzleId()==null||!st.puzzleIds.contains(i.getPuzzleId())||!seen.add(i.getPuzzleId()))throw new IllegalArgumentException("Fremde oder doppelte Rätsel-ID.");EscapePuzzle p=byId.get(i.getPuzzleId());if(p!=null&&matches(p,i.getSubmission())&&!st.revealed.contains(p.getId())){solved++;int a=Math.max(1,st.attempts.getOrDefault(p.getId(),1));double af=a==1?1:a==2?.6:.3;int hl=st.hints.getOrDefault(p.getId(),0);double hf=hl==0?1:hl==1?.8:.6;base+=(int)Math.round(p.getPoints()*af*hf);}}
|
||||
int opened=st.opened.size();base+=opened*50;int max=selectedRooms.values().stream().mapToInt(r->r.getPuzzles().stream().mapToInt(EscapePuzzle::getPoints).sum()+50).sum();boolean escaped=req.isEscaped()&&opened==st.roomIds.size();int bonus=escaped?Math.max(0,(int)Math.round(500.0*(s.getTimeLimitSeconds()-req.getDurationSeconds())/s.getTimeLimitSeconds())):0;int total=base+bonus;int stars=stars(base,max);ScoreResult saved=scores.recordScore(user,GAME,s.getSlug(),total,solved,req.getDurationSeconds());httpClear(session);return new EscapeResult(total,base,max,bonus,solved,opened,escaped,stars,saved.getHighscore(),saved.isNewHighscore(),saved.getPlays());}
|
||||
int opened=st.opened.size();base+=opened*50;int max=selectedRooms.values().stream().mapToInt(r->r.getPuzzles().stream().mapToInt(EscapePuzzle::getPoints).sum()+50).sum();boolean escaped=req.isEscaped()&&opened==st.roomIds.size();int bonus=escaped?Math.max(0,(int)Math.round(500.0*(s.getTimeLimitSeconds()-req.getDurationSeconds())/s.getTimeLimitSeconds())):0;int total=base+bonus;int stars=stars(base,max);ScoreResult saved=scores.recordScore(user,GAME,s.getSlug(),total,solved,req.getDurationSeconds());if(escaped&&stars==3)achievements.awardSpecial(user,"Schleusenmeister","Entkomme aus SystemEscape mit drei Sternen.");httpClear(session);return new EscapeResult(total,base,max,bonus,solved,opened,escaped,stars,saved.getHighscore(),saved.isNewHighscore(),saved.getPlays());}
|
||||
private void httpClear(HttpSession s){s.removeAttribute("codeescape.run");}
|
||||
private boolean matches(EscapePuzzle p,JsonNode sub){JsonNode j=parse(p.getPayload());if(j==null||sub==null)return false;return switch(p.getType()){case"code"->strings(j,"solutions").stream().anyMatch(x->eq(x,sub.path("answer").asText(""),j.path("caseSensitive").asBoolean(false)));case"choice"->eq(j.path("correct").asText(),sub.path("answer").asText(),false);case"order"->listEq(strings(j,"solution"),strings(sub,"items"));case"match"->pairEq(j,sub);default->false;};}
|
||||
private boolean pairEq(JsonNode j,JsonNode s){Map<String,String>w=new HashMap<>();for(JsonNode p:array(j,"pairs"))if(p.size()>1)w.put(norm(p.get(0).asText()),norm(p.get(1).asText()));for(JsonNode p:array(s,"pairs"))if(p.size()>1&&!Objects.equals(w.get(norm(p.get(0).asText())),norm(p.get(1).asText())))return false;return arraySize(s,"pairs")==w.size();}private int arraySize(JsonNode n,String f){JsonNode a=n==null?null:n.get(f);return a!=null&&a.isArray()?a.size():0;}private boolean listEq(List<String>a,List<String>b){if(a.size()!=b.size())return false;for(int i=0;i<a.size();i++)if(!eq(a.get(i),b.get(i),false))return false;return true;}private static boolean eq(String a,String b,boolean cs){return cs?norm(a).equals(norm(b)):norm(a).equalsIgnoreCase(norm(b));}private static String norm(String s){return s==null?"":s.trim().replaceAll("\\s+"," ");}private int stars(int s,int m){if(s<=0||m<=0)return 0;double r=(double)s/m;return r>=.8?3:r>=.5?2:1;}private JsonNode parse(String j){try{return mapper.readTree(j);}catch(Exception e){return null;}}private Iterable<JsonNode> array(JsonNode n,String f){JsonNode a=n==null?null:n.get(f);return a!=null&&a.isArray()?a:mapper.createArrayNode();}private List<String> strings(JsonNode n,String f){List<String>o=new ArrayList<>();for(JsonNode x:array(n,f))o.add(x.asText());return o;}private ArrayNode shuffle(List<String>x){List<String>c=new ArrayList<>(x);Collections.shuffle(c);ArrayNode a=mapper.createArrayNode();c.forEach(a::add);return a;}private void copy(JsonNode s,ObjectNode t,String f){if(s.hasNonNull(f))t.put(f,s.get(f).asText());}
|
||||
|
||||
Reference in New Issue
Block a user