From 509297cc823d3bbbc0180420787e1a36edb5c1db Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Fri, 11 Apr 2025 20:28:18 +0200 Subject: [PATCH 1/5] Clear up 1 vs 1 calculation formula --- .../kickerelo/service/EloCalculationService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java index 90d530c..d462a0d 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java @@ -21,13 +21,13 @@ public class EloCalculationService { public void updateElo1vs1(Spieler gewinner, Spieler verlierer, short toreVerlierer) { final float baseK = 50; final float reductionPerGoal = 0.1f * baseK; - final float finalK = baseK - (reductionPerGoal * toreVerlierer); - float expectedScoreWinner = (float) (1 / (1 + Math.pow(10, (verlierer.getElo1vs1() - gewinner.getElo1vs1()) / 400))); - float expectedScoreLoser = (float) (1 / (1 + Math.pow(10, (gewinner.getElo1vs1() - verlierer.getElo1vs1()) / 400))); - gewinner.setElo1vs1(gewinner.getElo1vs1() + finalK * (1-expectedScoreWinner)); - verlierer.setElo1vs1(verlierer.getElo1vs1() - finalK * expectedScoreLoser); + double x = Math.pow(10, (verlierer.getElo1vs1() - gewinner.getElo1vs1()) / 400); + float eloChange = (float) (finalK * x / (x + 1)); + + gewinner.setElo1vs1(gewinner.getElo1vs1() + eloChange); + verlierer.setElo1vs1(verlierer.getElo1vs1() - eloChange); } /** From f38b5b952fa6655a131349be1c4e5ac167fd09ec Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Fri, 9 May 2025 15:42:04 +0200 Subject: [PATCH 2/5] Add 2 vs 2 win rates --- .../kickerelo/layout/KickerAppLayout.java | 3 +- .../repository/Ergebnis2vs2Repository.java | 5 +++ .../kickerelo/service/Stat2vs2Service.java | 45 +++++++++++++++++++ .../kickerelo/kickerelo/util/Position.java | 5 +++ .../kickerelo/views/Stat2vs2View.java | 42 +++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/kickerelo/kickerelo/service/Stat2vs2Service.java create mode 100644 src/main/java/org/kickerelo/kickerelo/util/Position.java create mode 100644 src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java diff --git a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java index ee8a1e8..ef3ff53 100644 --- a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java +++ b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java @@ -39,7 +39,8 @@ public class KickerAppLayout extends AppLayout { nav2.setCollapsible(true); nav2.addItem(new SideNavItem("Ergebnis eintragen", Enter2vs2View.class, VaadinIcon.EDIT.create()), new SideNavItem("ELO-Graph", Graph2vs2View.class, VaadinIcon.BAR_CHART.create()), - new SideNavItem("Historie", History2vs2View.class, VaadinIcon.RECORDS.create())); + new SideNavItem("Historie", History2vs2View.class, VaadinIcon.RECORDS.create()), + new SideNavItem("Statistik", Stat2vs2View.class, VaadinIcon.ABACUS.create())); Image githubLogo = new Image("github-mark.png", "Github"); diff --git a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java index 3e9c3f9..ab3a421 100644 --- a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java +++ b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java @@ -1,9 +1,14 @@ package org.kickerelo.kickerelo.repository; import org.kickerelo.kickerelo.data.Ergebnis2vs2; +import org.kickerelo.kickerelo.data.Spieler; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface Ergebnis2vs2Repository extends JpaRepository { + int countByGewinnerHinten(Spieler gewinnerHinten); + int countByGewinnerVorn(Spieler gewinnerVon); + int countByVerliererHinten(Spieler verliererHinten); + int countByVerliererVorn(Spieler verlierVon); } diff --git a/src/main/java/org/kickerelo/kickerelo/service/Stat2vs2Service.java b/src/main/java/org/kickerelo/kickerelo/service/Stat2vs2Service.java new file mode 100644 index 0000000..dd1f863 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/service/Stat2vs2Service.java @@ -0,0 +1,45 @@ +package org.kickerelo.kickerelo.service; + +import org.kickerelo.kickerelo.data.Spieler; +import org.kickerelo.kickerelo.exception.NoSuchPlayerException; +import org.kickerelo.kickerelo.repository.Ergebnis2vs2Repository; +import org.kickerelo.kickerelo.repository.SpielerRepository; +import org.kickerelo.kickerelo.util.Position; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class Stat2vs2Service { + Ergebnis2vs2Repository ergebnis2vs2Repository; + SpielerRepository spielerRepository; + + public Stat2vs2Service(Ergebnis2vs2Repository ergebnis2vs2Repository, SpielerRepository spielerRepository) { + this.ergebnis2vs2Repository = ergebnis2vs2Repository; + this.spielerRepository = spielerRepository; + } + + public Float getWinrate(Spieler s, Position p) { + int wins, losses; + switch (p) { + case BACK: + wins = ergebnis2vs2Repository.countByGewinnerHinten(s); + losses = ergebnis2vs2Repository.countByVerliererHinten(s); + break; + case FRONT: + wins = ergebnis2vs2Repository.countByGewinnerVorn(s); + losses = ergebnis2vs2Repository.countByVerliererVorn(s); + break; + default: + wins = 0; + losses = 0; + } + return (float) (100 * wins) / (wins+losses); + } + + public Float getWinrate(String playerName, Position p) { + Optional spieler = spielerRepository.findByName(playerName); + if (spieler.isEmpty()) throw new NoSuchPlayerException(playerName); + return getWinrate(spieler.get(), p); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/util/Position.java b/src/main/java/org/kickerelo/kickerelo/util/Position.java new file mode 100644 index 0000000..7fc835e --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/util/Position.java @@ -0,0 +1,5 @@ +package org.kickerelo.kickerelo.util; + +public enum Position { + FRONT, BACK +} diff --git a/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java new file mode 100644 index 0000000..c78a8f8 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java @@ -0,0 +1,42 @@ +package org.kickerelo.kickerelo.views; + +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import org.kickerelo.kickerelo.data.Spieler; +import org.kickerelo.kickerelo.service.KickerEloService; +import org.kickerelo.kickerelo.service.Stat2vs2Service; +import org.kickerelo.kickerelo.util.Position; + +@Route("stat2vs2") +public class Stat2vs2View extends VerticalLayout { + Stat2vs2Service stat2vs2Service; + KickerEloService kickerEloService; + H2 subheading; + ComboBox selector; + Paragraph winrateBack; + Paragraph winrateFront; + public Stat2vs2View(Stat2vs2Service service, KickerEloService kickerService) { + this.stat2vs2Service = service; + this.kickerEloService = kickerService; + subheading = new H2("2 vs 2 Ergebnis"); + selector = new ComboBox<>("Spieler"); + selector.setItems(kickerService.getSpielerEntities()); + selector.addValueChangeListener(event -> { + updateData(selector.getValue()); + }); + winrateBack = new Paragraph(""); + winrateFront = new Paragraph(""); + + add(subheading, selector, winrateBack, winrateFront); + } + + private void updateData(Spieler s) { + Float rateBack = stat2vs2Service.getWinrate(s, Position.BACK); + Float rateFront = stat2vs2Service.getWinrate(s, Position.FRONT); + winrateBack.setText("Winrate hinten: " + (rateBack.isNaN() ? "-" : rateBack)); + winrateFront.setText("Winrate vorne: " + (rateFront.isNaN() ? "-" : rateFront)); + } +} From 2e5a4189862d7378c6e3fa267e4c130f0b964f1c Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Sun, 11 May 2025 00:39:00 +0200 Subject: [PATCH 3/5] Use progressbars for winrates --- .../repository/Ergebnis2vs2Repository.java | 4 ++ .../kickerelo/service/Stat2vs2Service.java | 13 ++--- .../kickerelo/views/Stat2vs2View.java | 52 ++++++++++++++----- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java index ab3a421..e3163dc 100644 --- a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java +++ b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java @@ -11,4 +11,8 @@ public interface Ergebnis2vs2Repository extends JpaRepository spieler = spielerRepository.findByName(playerName); - if (spieler.isEmpty()) throw new NoSuchPlayerException(playerName); - return getWinrate(spieler.get(), p); + public Float getFrontRate(Spieler s) { + int numFront = ergebnis2vs2Repository.countByGewinnerVornOrGewinnerHinten(s, s); + int numAll = ergebnis2vs2Repository.countByGewinnerVornOrGewinnerHintenOrVerliererVornOrVerliererHinten(s, s, s, s); + return (float) numFront / numAll; } } diff --git a/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java index c78a8f8..deb7af9 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java @@ -2,8 +2,10 @@ package org.kickerelo.kickerelo.views; import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.html.H2; -import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.html.NativeLabel; import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.progressbar.ProgressBar; +import com.vaadin.flow.component.progressbar.ProgressBarVariant; import com.vaadin.flow.router.Route; import org.kickerelo.kickerelo.data.Spieler; import org.kickerelo.kickerelo.service.KickerEloService; @@ -16,27 +18,49 @@ public class Stat2vs2View extends VerticalLayout { KickerEloService kickerEloService; H2 subheading; ComboBox selector; - Paragraph winrateBack; - Paragraph winrateFront; + ProgressBar frontRate = new ProgressBar(); + NativeLabel frontRateText = new NativeLabel(); + ProgressBar winRateFront = new ProgressBar(); + NativeLabel winRateFrontText = new NativeLabel(); + ProgressBar winRateBack = new ProgressBar(); + NativeLabel winRateBackText = new NativeLabel(); + public Stat2vs2View(Stat2vs2Service service, KickerEloService kickerService) { this.stat2vs2Service = service; this.kickerEloService = kickerService; subheading = new H2("2 vs 2 Ergebnis"); selector = new ComboBox<>("Spieler"); selector.setItems(kickerService.getSpielerEntities()); - selector.addValueChangeListener(event -> { - updateData(selector.getValue()); - }); - winrateBack = new Paragraph(""); - winrateFront = new Paragraph(""); - - add(subheading, selector, winrateBack, winrateFront); + selector.addValueChangeListener(event -> updateData(selector.getValue())); + add(subheading, selector, frontRateText, frontRate, winRateFrontText, winRateFront, winRateBackText, winRateBack); } private void updateData(Spieler s) { - Float rateBack = stat2vs2Service.getWinrate(s, Position.BACK); - Float rateFront = stat2vs2Service.getWinrate(s, Position.FRONT); - winrateBack.setText("Winrate hinten: " + (rateBack.isNaN() ? "-" : rateBack)); - winrateFront.setText("Winrate vorne: " + (rateFront.isNaN() ? "-" : rateFront)); + updateFrontRate(s); + updateFrontWinrate(s); + updateBackWinrate(s); + } + + private void updateFrontRate(Spieler s) { + String text = "Anteil Spiele vorne: "; + Float rateFrontGames = stat2vs2Service.getFrontRate(s); + frontRate.setValue(rateFrontGames); + frontRateText.setText(rateFrontGames.isNaN() ? text + "-" : text + String.format("%.2f", rateFrontGames * 100) + "%"); + } + + private void updateFrontWinrate(Spieler s) { + String text = "Winrate vorne: "; + Float winRate = stat2vs2Service.getWinrate(s, Position.FRONT); + winRateFront.setValue(winRate); + winRateFront.addThemeVariants((winRate > 0.5f ? ProgressBarVariant.LUMO_SUCCESS : ProgressBarVariant.LUMO_ERROR)); + winRateFrontText.setText(winRate.isNaN() ? text + "-" : text + String.format("%.2f", winRate * 100) + "%"); + } + + private void updateBackWinrate(Spieler s) { + String text = "Winrate hinten: "; + Float winRate = stat2vs2Service.getWinrate(s, Position.BACK); + winRateBack.setValue(winRate); + winRateBack.addThemeVariants((winRate > 0.5f ? ProgressBarVariant.LUMO_SUCCESS : ProgressBarVariant.LUMO_ERROR)); + winRateBackText.setText(winRate.isNaN() ? text + "-" : text + String.format("%.2f", winRate * 100) + "%"); } } From 6cb58e0518d05fe133aae3f851168d109f39e4c1 Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Sun, 11 May 2025 00:47:29 +0200 Subject: [PATCH 4/5] Fix progbar themes --- src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java index deb7af9..b13b19e 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java @@ -52,6 +52,7 @@ public class Stat2vs2View extends VerticalLayout { String text = "Winrate vorne: "; Float winRate = stat2vs2Service.getWinrate(s, Position.FRONT); winRateFront.setValue(winRate); + winRateBack.removeThemeVariants(ProgressBarVariant.LUMO_SUCCESS, ProgressBarVariant.LUMO_ERROR); winRateFront.addThemeVariants((winRate > 0.5f ? ProgressBarVariant.LUMO_SUCCESS : ProgressBarVariant.LUMO_ERROR)); winRateFrontText.setText(winRate.isNaN() ? text + "-" : text + String.format("%.2f", winRate * 100) + "%"); } @@ -60,6 +61,7 @@ public class Stat2vs2View extends VerticalLayout { String text = "Winrate hinten: "; Float winRate = stat2vs2Service.getWinrate(s, Position.BACK); winRateBack.setValue(winRate); + winRateBack.removeThemeVariants(ProgressBarVariant.LUMO_SUCCESS, ProgressBarVariant.LUMO_ERROR); winRateBack.addThemeVariants((winRate > 0.5f ? ProgressBarVariant.LUMO_SUCCESS : ProgressBarVariant.LUMO_ERROR)); winRateBackText.setText(winRate.isNaN() ? text + "-" : text + String.format("%.2f", winRate * 100) + "%"); } From 518361cade01039824dc7b6588774a68b4948d69 Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Sun, 11 May 2025 00:51:55 +0200 Subject: [PATCH 5/5] Fix progbar themes again --- src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java index b13b19e..7bfa90e 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Stat2vs2View.java @@ -52,7 +52,7 @@ public class Stat2vs2View extends VerticalLayout { String text = "Winrate vorne: "; Float winRate = stat2vs2Service.getWinrate(s, Position.FRONT); winRateFront.setValue(winRate); - winRateBack.removeThemeVariants(ProgressBarVariant.LUMO_SUCCESS, ProgressBarVariant.LUMO_ERROR); + winRateFront.removeThemeVariants(ProgressBarVariant.LUMO_SUCCESS, ProgressBarVariant.LUMO_ERROR); winRateFront.addThemeVariants((winRate > 0.5f ? ProgressBarVariant.LUMO_SUCCESS : ProgressBarVariant.LUMO_ERROR)); winRateFrontText.setText(winRate.isNaN() ? text + "-" : text + String.format("%.2f", winRate * 100) + "%"); }