diff --git a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java index d462a0d..f829b91 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java @@ -12,13 +12,19 @@ public class EloCalculationService { private final float initialElo1vs1 = 1500; private final float initialElo2vs2 = 1500; + private final EloChangeService eloChangeService; + + EloCalculationService(EloChangeService eloChangeService) { + this.eloChangeService = eloChangeService; + } + /** * Updates the 1 vs 1 ELOs of the players according to the result of the game. * @param gewinner The entity representing the winning player * @param verlierer The entity representing the losing player * @param toreVerlierer The number of goals of the losing player */ - public void updateElo1vs1(Spieler gewinner, Spieler verlierer, short toreVerlierer) { + public void updateElo1vs1(long gameId, Spieler gewinner, Spieler verlierer, short toreVerlierer) { final float baseK = 50; final float reductionPerGoal = 0.1f * baseK; final float finalK = baseK - (reductionPerGoal * toreVerlierer); @@ -28,17 +34,20 @@ public class EloCalculationService { gewinner.setElo1vs1(gewinner.getElo1vs1() + eloChange); verlierer.setElo1vs1(verlierer.getElo1vs1() - eloChange); + + eloChangeService.put1vs1Result(gameId, eloChange, -eloChange); } /** * Updates the 2 vs 2 ELOs of the players according to the result of the game + * @param gameId ID of the game * @param gewinnerVorn The winning offensive player * @param gewinnerHinten The winning defensive player * @param verliererVorn The losing offensive player * @param verliererHinten The losing defensive player * @param toreVerlierer The number of goals of the losing teams */ - public void updateElo2vs2(Spieler gewinnerVorn, Spieler gewinnerHinten, Spieler verliererVorn, Spieler verliererHinten, short toreVerlierer) { + public void updateElo2vs2(long gameId, Spieler gewinnerVorn, Spieler gewinnerHinten, Spieler verliererVorn, Spieler verliererHinten, short toreVerlierer) { final float baseK = 100; final double adjustedK = baseK * (1 - (0.1 * toreVerlierer)); var totalWinnerElo = gewinnerVorn.getElo2vs2() + gewinnerHinten.getElo2vs2(); @@ -60,6 +69,8 @@ public class EloCalculationService { gewinnerHinten.setElo2vs2((float) (gewinnerHinten.getElo2vs2() + winner2EloChange)); verliererVorn.setElo2vs2((float) (verliererVorn.getElo2vs2() + loser1EloChange)); verliererHinten.setElo2vs2((float) (verliererHinten.getElo2vs2() + loser2EloChange)); + + eloChangeService.put2vs2Result(gameId, winner1EloChange, winner2EloChange, loser1EloChange, loser2EloChange); } public float getInitialElo1vs1() { diff --git a/src/main/java/org/kickerelo/kickerelo/service/EloChangeService.java b/src/main/java/org/kickerelo/kickerelo/service/EloChangeService.java new file mode 100644 index 0000000..320531f --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/service/EloChangeService.java @@ -0,0 +1,37 @@ +package org.kickerelo.kickerelo.service; + +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +public class EloChangeService { + public record UpdateElo1vs1Change(double winnerEloChange, double loserEloChange) { + } + public record UpdateElo2vs2Change(double winnerFrontEloChange, double winnerBackEloChange, double loserFrontEloChange, double loserBackEloChange) { + } + + Map changes1vs1 = new HashMap<>(); + Map changes2vs2 = new HashMap<>(); + + public void put1vs1Result(long gameId, double winnerEloChange, double loserEloChange) { + changes1vs1.put(gameId, new UpdateElo1vs1Change(winnerEloChange, loserEloChange)); + } + + public void put2vs2Result(long gameId, double winnerFrontEloChange, double winnerBackEloChange, double loserFrontEloChange, double loserBackEloChange) { + changes2vs2.put(gameId, new UpdateElo2vs2Change(winnerFrontEloChange, winnerBackEloChange, loserFrontEloChange, loserBackEloChange)); + } + + public UpdateElo1vs1Change get1vs1Result(long gameId) { + var result = changes1vs1.get(gameId); + assert result != null; + return result; + } + + public UpdateElo2vs2Change get2vs2Result(long gameId) { + var result = changes2vs2.get(gameId); + assert result != null; + return result; + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java index f7d9ea0..7036392 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java @@ -24,15 +24,23 @@ import java.util.stream.Stream; */ @Service public class KickerEloService { - @Autowired private Ergebnis1vs1Repository ergebnis1vs1Repository; - @Autowired private Ergebnis2vs2Repository ergebnis2vs2Repository; - @Autowired private SpielerRepository spielerRepository; - @Autowired private EloCalculationService eloCalculationService; + public KickerEloService(Ergebnis1vs1Repository ergebnis1vs1Repository, + Ergebnis2vs2Repository ergebnis2vs2Repository, + SpielerRepository spielerRepository, + EloCalculationService eloCalculationService) { + this.ergebnis1vs1Repository = ergebnis1vs1Repository; + this.ergebnis2vs2Repository = ergebnis2vs2Repository; + this.spielerRepository = spielerRepository; + this.eloCalculationService = eloCalculationService; + recalculateAll1vs1(); + recalculateAll2vs2(); + } + /** * @return List of all player names sorted alphabetically */ @@ -70,7 +78,7 @@ public class KickerEloService { Ergebnis1vs1 ergebnis = new Ergebnis1vs1(gewinner, verlierer, toreVerlierer); ergebnis1vs1Repository.save(ergebnis); - eloCalculationService.updateElo1vs1(gewinner, verlierer, toreVerlierer); + eloCalculationService.updateElo1vs1(ergebnis.getId(), gewinner, verlierer, toreVerlierer); spielerRepository.save(gewinner); spielerRepository.save(verlierer); } @@ -108,7 +116,7 @@ public class KickerEloService { Ergebnis2vs2 ergebnis = new Ergebnis2vs2(gewinnerVorn, gewinnerHinten, verliererVorn, verliererHinten, toreVerlierer); ergebnis2vs2Repository.save(ergebnis); - eloCalculationService.updateElo2vs2(gewinnerVorn, gewinnerHinten, verliererVorn, verliererHinten, toreVerlierer); + eloCalculationService.updateElo2vs2(ergebnis.getId(), gewinnerVorn, gewinnerHinten, verliererVorn, verliererHinten, toreVerlierer); spielerRepository.save(gewinnerVorn); spielerRepository.save(gewinnerHinten); spielerRepository.save(verliererVorn); @@ -148,7 +156,8 @@ public class KickerEloService { } Stream results = ergebnis1vs1Repository.findAll().stream().sorted(new Ergebnis1vs1TimeComparator()); results.forEach(r -> { - eloCalculationService.updateElo1vs1(players.get(r.getGewinner().getId()), + eloCalculationService.updateElo1vs1(r.getId(), + players.get(r.getGewinner().getId()), players.get(r.getVerlierer().getId()), r.getToreVerlierer()); }); @@ -166,7 +175,8 @@ public class KickerEloService { } Stream results = ergebnis2vs2Repository.findAll().stream().sorted(new Ergebnis2vs2TimeComparator()); results.forEach(r -> { - eloCalculationService.updateElo2vs2(players.get(r.getGewinnerVorn().getId()), + eloCalculationService.updateElo2vs2(r.getId(), + players.get(r.getGewinnerVorn().getId()), players.get(r.getGewinnerHinten().getId()), players.get(r.getVerliererVorn().getId()), players.get(r.getVerliererHinten().getId()), diff --git a/src/main/java/org/kickerelo/kickerelo/views/History1vs1View.java b/src/main/java/org/kickerelo/kickerelo/views/History1vs1View.java index 2c1344f..b660fb7 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/History1vs1View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/History1vs1View.java @@ -10,18 +10,30 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.data.provider.SortDirection; +import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.data.renderer.LocalDateTimeRenderer; import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.router.Route; import org.kickerelo.kickerelo.data.Ergebnis1vs1; import org.kickerelo.kickerelo.repository.Ergebnis1vs1Repository; +import org.kickerelo.kickerelo.repository.Ergebnis2vs2Repository; +import org.kickerelo.kickerelo.service.EloChangeService; import java.util.List; +import static org.kickerelo.kickerelo.views.HistoryCommonView.formatEloChange; + @Route("history1vs1") public class History1vs1View extends VerticalLayout { + + private final Ergebnis1vs1Repository repo; + private final EloChangeService eloChangeService; + List res; - public History1vs1View(Ergebnis1vs1Repository repo) { + public History1vs1View(Ergebnis1vs1Repository repo, EloChangeService eloChangeService) { + this.repo = repo; + this.eloChangeService = eloChangeService; + setSizeFull(); H2 subheading = new H2("Spiele 1 vs 1"); res = repo.findAll(); @@ -49,8 +61,10 @@ public class History1vs1View extends VerticalLayout { grid.removeColumnByKey("id"); Grid.Column winnerColumn = grid.getColumnByKey("gewinner"); winnerColumn.setHeader("Gewinner"); + winnerColumn.setRenderer(new ComponentRenderer<>(ergebnis -> formatEloChange(ergebnis.getGewinner(), eloChangeService.get1vs1Result(ergebnis.getId()).winnerEloChange()))); Grid.Column loserColumn = grid.getColumnByKey("verlierer"); loserColumn.setHeader("Verlierer"); + loserColumn.setRenderer(new ComponentRenderer<>(ergebnis -> formatEloChange(ergebnis.getVerlierer(), eloChangeService.get1vs1Result(ergebnis.getId()).loserEloChange()))); Grid.Column goals = grid.getColumnByKey("toreVerlierer"); goals.setHeader("Verlierertore"); Grid.Column timestamp = grid.getColumnByKey("timestamp"); diff --git a/src/main/java/org/kickerelo/kickerelo/views/History2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/History2vs2View.java index 5daeb45..c57ebf0 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/History2vs2View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/History2vs2View.java @@ -11,17 +11,27 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.data.provider.SortDirection; +import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.data.renderer.LocalDateTimeRenderer; import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.router.Route; import org.kickerelo.kickerelo.data.Ergebnis2vs2; import org.kickerelo.kickerelo.repository.Ergebnis2vs2Repository; +import org.kickerelo.kickerelo.service.EloChangeService; import java.util.List; +import static org.kickerelo.kickerelo.views.HistoryCommonView.formatEloChange; + @Route("history2vs2") public class History2vs2View extends VerticalLayout { - public History2vs2View(Ergebnis2vs2Repository repo) { + + private final Ergebnis2vs2Repository repo; + private final EloChangeService eloChangeService; + + public History2vs2View(Ergebnis2vs2Repository repo, EloChangeService eloChangeService) { + this.repo = repo; + this.eloChangeService = eloChangeService; setSizeFull(); H2 subheading = new H2("Spiele 2 vs 2"); List res = repo.findAll(); @@ -63,12 +73,16 @@ public class History2vs2View extends VerticalLayout { grid.removeColumnByKey("id"); Grid.Column winnerFront = grid.getColumnByKey("gewinnerVorn"); winnerFront.setHeader("Gewinner vorne"); + winnerFront.setRenderer(new ComponentRenderer<>(ergebnis -> formatEloChange(ergebnis.getGewinnerVorn(), eloChangeService.get2vs2Result(ergebnis.getId()).winnerFrontEloChange()))); Grid.Column winnerBack = grid.getColumnByKey("gewinnerHinten"); winnerBack.setHeader("Gewinner hinten"); + winnerBack.setRenderer(new ComponentRenderer<>(ergebnis -> formatEloChange(ergebnis.getGewinnerHinten(), eloChangeService.get2vs2Result(ergebnis.getId()).winnerBackEloChange()))); Grid.Column loserFront = grid.getColumnByKey("verliererVorn"); loserFront.setHeader("Verlierer vorne"); + loserFront.setRenderer(new ComponentRenderer<>(ergebnis -> formatEloChange(ergebnis.getVerliererVorn(), eloChangeService.get2vs2Result(ergebnis.getId()).loserFrontEloChange()))); Grid.Column loserBack = grid.getColumnByKey("verliererHinten"); loserBack.setHeader("Verlierer hinten"); + loserBack.setRenderer(new ComponentRenderer<>(ergebnis -> formatEloChange(ergebnis.getVerliererHinten(), eloChangeService.get2vs2Result(ergebnis.getId()).loserBackEloChange()))); Grid.Column goals = grid.getColumnByKey("toreVerlierer"); goals.setHeader("Verlierertore"); Grid.Column timestamp = grid.getColumnByKey("timestamp"); diff --git a/src/main/java/org/kickerelo/kickerelo/views/HistoryCommonView.java b/src/main/java/org/kickerelo/kickerelo/views/HistoryCommonView.java new file mode 100644 index 0000000..98c2bf4 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/HistoryCommonView.java @@ -0,0 +1,24 @@ +package org.kickerelo.kickerelo.views; + +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Span; +import org.kickerelo.kickerelo.data.Spieler; + +public class HistoryCommonView { + private HistoryCommonView() {} + + static Div formatEloChange(Spieler spieler, double change) { + Div div = new Div(); + + Span nameSpan = new Span(spieler.getName() + " "); + String formattedChange = String.format("%+.2f", change); + Span changeSpan = new Span(formattedChange); + + changeSpan.getStyle().set("color", "var(--lumo-secondary-text-color)"); + changeSpan.getStyle().set("font-style", "italic"); + + div.add(nameSpan, changeSpan); + + return div; + } +}