From 819e26407e3002e8ae7e6e5ea911fdd136b8a885 Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Thu, 6 Feb 2025 00:29:37 +0100 Subject: [PATCH 1/9] Entry of both 1 vs 1 and 2 vs 2 results --- .../org/kickerelo/kickerelo/MainView.java | 1 + .../kickerelo/layout/KickerAppLayout.java | 35 +++++++ .../kickerelo/model/ResultInfo1vs1.java | 55 +++++++++++ .../kickerelo/model/ResultInfo2vs2.java | 93 +++++++++++++++++++ .../service/EloCalculationService.java | 20 ++++ .../{ => service}/KickerEloService.java | 26 +++++- .../kickerelo/views/Enter1vs1View.java | 51 ++++++++++ .../kickerelo/views/Enter2vs2View.java | 57 ++++++++++++ 8 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java create mode 100644 src/main/java/org/kickerelo/kickerelo/model/ResultInfo1vs1.java create mode 100644 src/main/java/org/kickerelo/kickerelo/model/ResultInfo2vs2.java create mode 100644 src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java rename src/main/java/org/kickerelo/kickerelo/{ => service}/KickerEloService.java (67%) create mode 100644 src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java create mode 100644 src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java diff --git a/src/main/java/org/kickerelo/kickerelo/MainView.java b/src/main/java/org/kickerelo/kickerelo/MainView.java index b9d0570..14fffc9 100644 --- a/src/main/java/org/kickerelo/kickerelo/MainView.java +++ b/src/main/java/org/kickerelo/kickerelo/MainView.java @@ -9,6 +9,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.router.Route; +import org.kickerelo.kickerelo.service.KickerEloService; /** * A sample Vaadin view class. diff --git a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java new file mode 100644 index 0000000..4b2a430 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java @@ -0,0 +1,35 @@ +package org.kickerelo.kickerelo.layout; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.applayout.DrawerToggle; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.orderedlayout.Scroller; +import com.vaadin.flow.component.sidenav.SideNav; +import com.vaadin.flow.component.tabs.Tab; +import com.vaadin.flow.component.tabs.Tabs; +import com.vaadin.flow.router.Layout; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.RouterLink; +import com.vaadin.flow.theme.lumo.LumoUtility; +import org.kickerelo.kickerelo.views.Enter1vs1View; +import org.kickerelo.kickerelo.views.Enter2vs2View; + +@Layout +public class KickerAppLayout extends AppLayout { + + public KickerAppLayout() { + DrawerToggle drawerToggle = new DrawerToggle(); + + H1 title = new H1("Kicker-ELO"); + title.getStyle().set("font-size", "var(--lumo-font-size-l)").set("margin", "0"); + + addToNavbar(drawerToggle, title); + + RouterLink enter1vs1 = new RouterLink("1 vs 1", Enter1vs1View.class); + RouterLink enter2vs2 = new RouterLink("2 vs 2", Enter2vs2View.class); + + Tabs tabs = new Tabs(new Tab(enter1vs1), new Tab(enter2vs2)); + tabs.setOrientation(Tabs.Orientation.VERTICAL); + addToDrawer(tabs); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/model/ResultInfo1vs1.java b/src/main/java/org/kickerelo/kickerelo/model/ResultInfo1vs1.java new file mode 100644 index 0000000..043f61c --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/model/ResultInfo1vs1.java @@ -0,0 +1,55 @@ +package org.kickerelo.kickerelo.model; + +public class ResultInfo1vs1 { + float oldEloWinner; + float oldEloLoser; + float newEloWinner; + float newEloLoser; + short loserGoals; + + public ResultInfo1vs1(float oldEloWinner, short loserGoals, float oldEloLoser) { + this.oldEloWinner = oldEloWinner; + this.loserGoals = loserGoals; + this.oldEloLoser = oldEloLoser; + } + + public float getOldEloWinner() { + return oldEloWinner; + } + + public void setOldEloWinner(float oldEloWinner) { + this.oldEloWinner = oldEloWinner; + } + + public float getOldEloLoser() { + return oldEloLoser; + } + + public void setOldEloLoser(float oldEloLoser) { + this.oldEloLoser = oldEloLoser; + } + + public float getNewEloWinner() { + return newEloWinner; + } + + public void setNewEloWinner(float newEloWinner) { + this.newEloWinner = newEloWinner; + } + + public float getNewEloLoser() { + return newEloLoser; + } + + public void setNewEloLoser(float newEloLoser) { + this.newEloLoser = newEloLoser; + } + + public short getLoserGoals() { + return loserGoals; + } + + public void setLoserGoals(short loserGoals) { + this.loserGoals = loserGoals; + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/model/ResultInfo2vs2.java b/src/main/java/org/kickerelo/kickerelo/model/ResultInfo2vs2.java new file mode 100644 index 0000000..96b5edc --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/model/ResultInfo2vs2.java @@ -0,0 +1,93 @@ +package org.kickerelo.kickerelo.model; + +public class ResultInfo2vs2 { + float oldEloWinnerFront; + float oldEloWinnerBack; + float oldEloLoserFront; + float oldEloLoserBack; + float newEloWinnerFront; + float newEloWinnerBack; + float newEloLoserFront; + float newEloLoserBack; + short loserGoals; + + public ResultInfo2vs2(float oldEloWinnerFront, float oldEloWinnerBack, float oldEloLoserFront, float oldEloLoserBack, short loserGoals) { + this.oldEloWinnerFront = oldEloWinnerFront; + this.oldEloWinnerBack = oldEloWinnerBack; + this.oldEloLoserFront = oldEloLoserFront; + this.oldEloLoserBack = oldEloLoserBack; + this.loserGoals = loserGoals; + } + + public float getOldEloWinnerFront() { + return oldEloWinnerFront; + } + + public void setOldEloWinnerFront(float oldEloWinnerFront) { + this.oldEloWinnerFront = oldEloWinnerFront; + } + + public float getOldEloWinnerBack() { + return oldEloWinnerBack; + } + + public void setOldEloWinnerBack(float oldEloWinnerBack) { + this.oldEloWinnerBack = oldEloWinnerBack; + } + + public float getOldEloLoserFront() { + return oldEloLoserFront; + } + + public void setOldEloLoserFront(float oldEloLoserFront) { + this.oldEloLoserFront = oldEloLoserFront; + } + + public float getOldEloLoserBack() { + return oldEloLoserBack; + } + + public void setOldEloLoserBack(float oldEloLoserBack) { + this.oldEloLoserBack = oldEloLoserBack; + } + + public float getNewEloWinnerFront() { + return newEloWinnerFront; + } + + public void setNewEloWinnerFront(float newEloWinnerFront) { + this.newEloWinnerFront = newEloWinnerFront; + } + + public float getNewEloWinnerBack() { + return newEloWinnerBack; + } + + public void setNewEloWinnerBack(float newEloWinnerBack) { + this.newEloWinnerBack = newEloWinnerBack; + } + + public float getNewEloLoserFront() { + return newEloLoserFront; + } + + public void setNewEloLoserFront(float newEloLoserFront) { + this.newEloLoserFront = newEloLoserFront; + } + + public float getNewEloLoserBack() { + return newEloLoserBack; + } + + public void setNewEloLoserBack(float newEloLoserBack) { + this.newEloLoserBack = newEloLoserBack; + } + + public short getLoserGoals() { + return loserGoals; + } + + public void setLoserGoals(short loserGoals) { + this.loserGoals = loserGoals; + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java new file mode 100644 index 0000000..54d1f1d --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java @@ -0,0 +1,20 @@ +package org.kickerelo.kickerelo.service; + +import org.kickerelo.kickerelo.model.ResultInfo1vs1; +import org.kickerelo.kickerelo.model.ResultInfo2vs2; +import org.springframework.stereotype.Service; + +@Service +public class EloCalculationService { + public void updateElo1vs1(ResultInfo1vs1 result) { + result.setNewEloWinner(result.getOldEloWinner() + 1); + result.setNewEloLoser(result.getOldEloLoser() - 1); + } + + public void updateElo2vs2(ResultInfo2vs2 result) { + result.setNewEloWinnerFront(result.getOldEloWinnerFront() + 1); + result.setNewEloWinnerBack(result.getOldEloWinnerBack() + 1); + result.setNewEloLoserFront(result.getOldEloLoserFront() - 1); + result.setNewEloLoserBack(result.getOldEloLoserBack() - 1); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/KickerEloService.java b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java similarity index 67% rename from src/main/java/org/kickerelo/kickerelo/KickerEloService.java rename to src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java index 0edfe88..7356ff2 100644 --- a/src/main/java/org/kickerelo/kickerelo/KickerEloService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java @@ -1,8 +1,11 @@ -package org.kickerelo.kickerelo; +package org.kickerelo.kickerelo.service; +import org.kickerelo.kickerelo.NoSuchPlayerException; import org.kickerelo.kickerelo.data.Ergebnis1vs1; import org.kickerelo.kickerelo.data.Ergebnis2vs2; import org.kickerelo.kickerelo.data.Spieler; +import org.kickerelo.kickerelo.model.ResultInfo1vs1; +import org.kickerelo.kickerelo.model.ResultInfo2vs2; import org.kickerelo.kickerelo.repository.Ergebnis1vs1Repository; import org.kickerelo.kickerelo.repository.Ergebnis2vs2Repository; import org.kickerelo.kickerelo.repository.SpielerRepository; @@ -19,6 +22,8 @@ public class KickerEloService { private Ergebnis2vs2Repository ergebnis2vs2Repository; @Autowired private SpielerRepository spielerRepository; + @Autowired + private EloCalculationService eloCalculationService; public List getSpielerNamen() { return spielerRepository.findAll().stream().map(Spieler::getName).toList(); @@ -38,7 +43,13 @@ public class KickerEloService { ergebnis1vs1Repository.save(ergebnis); - // Compute the new ELO and update the Spieler entities + ResultInfo1vs1 result = new ResultInfo1vs1(gewinner.getElo(), ergebnis.getToreVerlierer(), verlierer.getElo()); + eloCalculationService.updateElo1vs1(result); + gewinner.setElo(result.getNewEloWinner()); + spielerRepository.save(gewinner); + verlierer.setElo(result.getNewEloLoser()); + spielerRepository.save(verlierer); + } public void enterResult2vs2(String gewinnerNameVorn, String gewinnerNameHinten, @@ -61,7 +72,16 @@ public class KickerEloService { ergebnis2vs2Repository.save(ergebnis); - // Compute the new ELO, update the Spieler entitities + ResultInfo2vs2 result = new ResultInfo2vs2(gewinnerVorn.getElo(), gewinnerHinten.getElo(), verliererVorn.getElo(), verliererHinten.getElo(), toreVerlierer); + eloCalculationService.updateElo2vs2(result); + gewinnerVorn.setElo(result.getNewEloWinnerFront()); + spielerRepository.save(gewinnerVorn); + gewinnerHinten.setElo(result.getNewEloWinnerBack()); + spielerRepository.save(gewinnerHinten); + verliererVorn.setElo(result.getNewEloLoserFront()); + spielerRepository.save(verliererVorn); + verliererHinten.setElo(result.getNewEloLoserBack()); + spielerRepository.save(verliererHinten); } public void addSpieler(String name) { diff --git a/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java b/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java new file mode 100644 index 0000000..cd0c690 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java @@ -0,0 +1,51 @@ +package org.kickerelo.kickerelo.views; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.router.Route; +import org.kickerelo.kickerelo.NoSuchPlayerException; +import org.kickerelo.kickerelo.service.KickerEloService; + +@Route(value = "enter1vs1") +public class Enter1vs1View extends VerticalLayout { + + public Enter1vs1View(KickerEloService eloService) { + H2 subheading = new H2("1 vs 1 Ergebnis"); + + ComboBox winnerSelect = new ComboBox<>("Gewinner"); + winnerSelect.setItems(eloService.getSpielerNamen()); + winnerSelect.setPlaceholder("Spieler auswählen"); + + ComboBox loserSelect = new ComboBox<>("Verlierer"); + loserSelect.setItems(eloService.getSpielerNamen()); + loserSelect.setPlaceholder("Spieler auswählen"); + + IntegerField loserGoals = new IntegerField("Tore des Verlierers"); + loserGoals.setMin(0); + loserGoals.setMax(9); + loserGoals.setValue(0); + loserGoals.setStepButtonsVisible(true); + + Button saveButton = new Button("Speichern", e -> { + try { + eloService.enterResult1vs1(winnerSelect.getValue(), loserSelect.getValue(), loserGoals.getValue().shortValue()); + Notification.show("Gespeichert").addThemeVariants(NotificationVariant.LUMO_SUCCESS); + } catch (NoSuchPlayerException err) { + Notification.show("Konnte nicht gespeichert werden").addThemeVariants(NotificationVariant.LUMO_ERROR); + } + }); + + + + // Use custom CSS classes to apply styling. This is defined in + // styles.css. + addClassName("centered-content"); + + add(subheading, winnerSelect, loserSelect, loserGoals, saveButton); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java new file mode 100644 index 0000000..ed46440 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java @@ -0,0 +1,57 @@ +package org.kickerelo.kickerelo.views; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.router.Route; +import org.kickerelo.kickerelo.NoSuchPlayerException; +import org.kickerelo.kickerelo.service.KickerEloService; + +@Route(value = "enter2vs2") +public class Enter2vs2View extends VerticalLayout { + public Enter2vs2View(KickerEloService eloService) { + H2 subheading = new H2("2 vs 2 Ergebnis"); + + ComboBox winnerFrontSelect = new ComboBox<>("Gewinner vorne"); + winnerFrontSelect.setItems(eloService.getSpielerNamen()); + winnerFrontSelect.setPlaceholder("Spieler auswählen"); + + ComboBox winnerBackSelect = new ComboBox<>("Gewinner hinten"); + winnerBackSelect.setItems(eloService.getSpielerNamen()); + winnerBackSelect.setPlaceholder("Spieler auswählen"); + + ComboBox loserFrontSelect = new ComboBox<>("Verlierer vorne"); + loserFrontSelect.setItems(eloService.getSpielerNamen()); + loserFrontSelect.setPlaceholder("Spieler auswählen"); + + ComboBox loserBackSelect = new ComboBox<>("Verlierer hinten"); + loserBackSelect.setItems(eloService.getSpielerNamen()); + loserBackSelect.setPlaceholder("Spieler auswählen"); + + IntegerField loserGoals = new IntegerField("Tore des Verlierers"); + loserGoals.setMin(0); + loserGoals.setMax(9); + loserGoals.setValue(0); + loserGoals.setStepButtonsVisible(true); + + Button saveButton = new Button("Speichern", e -> { + try { + eloService.enterResult2vs2(winnerFrontSelect.getValue(), winnerBackSelect.getValue(), loserFrontSelect.getValue(), loserBackSelect.getValue(), loserGoals.getValue().shortValue()); + Notification.show("Gespeichert").addThemeVariants(NotificationVariant.LUMO_SUCCESS); + } catch (NoSuchPlayerException err) { + Notification.show("Konnte nicht gespeichert werden").addThemeVariants(NotificationVariant.LUMO_ERROR); + } + }); + + + // Use custom CSS classes to apply styling. This is defined in + // styles.css. + addClassName("centered-content"); + + add(subheading, winnerFrontSelect, winnerBackSelect, loserFrontSelect, loserBackSelect, loserGoals, saveButton); + } +} From 3f890f06d803c842d3d6448e49ac9e5f6b17f0ce Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Thu, 6 Feb 2025 00:54:59 +0100 Subject: [PATCH 2/9] Add input checks for player names and duplicates. --- .../org/kickerelo/kickerelo/MainView.java | 1 + .../exception/DuplicatePlayerException.java | 7 ++++++ .../NoSuchPlayerException.java | 2 +- .../exception/PlayerNameNotSetException.java | 7 ++++++ .../kickerelo/service/KickerEloService.java | 25 ++++++++++++++++++- .../kickerelo/views/Enter1vs1View.java | 10 ++++++-- .../kickerelo/views/Enter2vs2View.java | 10 ++++++-- 7 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/kickerelo/kickerelo/exception/DuplicatePlayerException.java rename src/main/java/org/kickerelo/kickerelo/{ => exception}/NoSuchPlayerException.java (77%) create mode 100644 src/main/java/org/kickerelo/kickerelo/exception/PlayerNameNotSetException.java diff --git a/src/main/java/org/kickerelo/kickerelo/MainView.java b/src/main/java/org/kickerelo/kickerelo/MainView.java index 14fffc9..0e030f1 100644 --- a/src/main/java/org/kickerelo/kickerelo/MainView.java +++ b/src/main/java/org/kickerelo/kickerelo/MainView.java @@ -9,6 +9,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.router.Route; +import org.kickerelo.kickerelo.exception.NoSuchPlayerException; import org.kickerelo.kickerelo.service.KickerEloService; /** diff --git a/src/main/java/org/kickerelo/kickerelo/exception/DuplicatePlayerException.java b/src/main/java/org/kickerelo/kickerelo/exception/DuplicatePlayerException.java new file mode 100644 index 0000000..5a15e17 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/exception/DuplicatePlayerException.java @@ -0,0 +1,7 @@ +package org.kickerelo.kickerelo.exception; + +public class DuplicatePlayerException extends RuntimeException { + public DuplicatePlayerException(String message) { + super(message); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/NoSuchPlayerException.java b/src/main/java/org/kickerelo/kickerelo/exception/NoSuchPlayerException.java similarity index 77% rename from src/main/java/org/kickerelo/kickerelo/NoSuchPlayerException.java rename to src/main/java/org/kickerelo/kickerelo/exception/NoSuchPlayerException.java index 2fbbe3f..b2d08cd 100644 --- a/src/main/java/org/kickerelo/kickerelo/NoSuchPlayerException.java +++ b/src/main/java/org/kickerelo/kickerelo/exception/NoSuchPlayerException.java @@ -1,4 +1,4 @@ -package org.kickerelo.kickerelo; +package org.kickerelo.kickerelo.exception; public class NoSuchPlayerException extends RuntimeException { public NoSuchPlayerException(String message) { diff --git a/src/main/java/org/kickerelo/kickerelo/exception/PlayerNameNotSetException.java b/src/main/java/org/kickerelo/kickerelo/exception/PlayerNameNotSetException.java new file mode 100644 index 0000000..eda8b0b --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/exception/PlayerNameNotSetException.java @@ -0,0 +1,7 @@ +package org.kickerelo.kickerelo.exception; + +public class PlayerNameNotSetException extends RuntimeException { + public PlayerNameNotSetException(String message) { + super(message); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java index 7356ff2..73931af 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java @@ -1,9 +1,11 @@ package org.kickerelo.kickerelo.service; -import org.kickerelo.kickerelo.NoSuchPlayerException; +import org.kickerelo.kickerelo.exception.DuplicatePlayerException; +import org.kickerelo.kickerelo.exception.NoSuchPlayerException; import org.kickerelo.kickerelo.data.Ergebnis1vs1; import org.kickerelo.kickerelo.data.Ergebnis2vs2; import org.kickerelo.kickerelo.data.Spieler; +import org.kickerelo.kickerelo.exception.PlayerNameNotSetException; import org.kickerelo.kickerelo.model.ResultInfo1vs1; import org.kickerelo.kickerelo.model.ResultInfo2vs2; import org.kickerelo.kickerelo.repository.Ergebnis1vs1Repository; @@ -31,6 +33,13 @@ public class KickerEloService { public void enterResult1vs1(String gewinnerName, String verliererName, short toreVerlierer) { + if (gewinnerName == null || verliererName == null) { + throw new PlayerNameNotSetException("Alle Namen müssen gesetzt sein"); + } + + if (gewinnerName.equals(verliererName)) { + throw new DuplicatePlayerException("winner and loser identical"); + } Spieler gewinner = spielerRepository.findByName(gewinnerName) .orElseThrow(() -> new NoSuchPlayerException(gewinnerName)); @@ -56,6 +65,20 @@ public class KickerEloService { String verliererNameVorn, String verliererNameHinten, short toreVerlierer) { + if (gewinnerNameVorn == null || gewinnerNameHinten == null + || verliererNameVorn == null || verliererNameHinten == null) { + throw new PlayerNameNotSetException("Alle Namen müssen gesetzt sein"); + } + + if (gewinnerNameVorn.equals(gewinnerNameHinten) || + gewinnerNameVorn.equals(verliererNameVorn) || + gewinnerNameVorn.equals(verliererNameHinten) || + gewinnerNameHinten.equals(verliererNameVorn) || + gewinnerNameHinten.equals(verliererNameHinten) || + verliererNameVorn.equals(verliererNameHinten)) { + throw new DuplicatePlayerException("players must not be identical"); + } + Spieler gewinnerVorn = spielerRepository.findByName(gewinnerNameVorn) .orElseThrow(() -> new NoSuchPlayerException(gewinnerNameVorn)); diff --git a/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java b/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java index cd0c690..1ecd23b 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java @@ -8,7 +8,9 @@ import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.router.Route; -import org.kickerelo.kickerelo.NoSuchPlayerException; +import org.kickerelo.kickerelo.exception.DuplicatePlayerException; +import org.kickerelo.kickerelo.exception.NoSuchPlayerException; +import org.kickerelo.kickerelo.exception.PlayerNameNotSetException; import org.kickerelo.kickerelo.service.KickerEloService; @Route(value = "enter1vs1") @@ -36,7 +38,11 @@ public class Enter1vs1View extends VerticalLayout { eloService.enterResult1vs1(winnerSelect.getValue(), loserSelect.getValue(), loserGoals.getValue().shortValue()); Notification.show("Gespeichert").addThemeVariants(NotificationVariant.LUMO_SUCCESS); } catch (NoSuchPlayerException err) { - Notification.show("Konnte nicht gespeichert werden").addThemeVariants(NotificationVariant.LUMO_ERROR); + Notification.show("Unbekannter Spieler").addThemeVariants(NotificationVariant.LUMO_ERROR); + } catch (DuplicatePlayerException err) { + Notification.show("Alle Spieler müssen paarweise verschieden sein").addThemeVariants(NotificationVariant.LUMO_ERROR); + } catch (PlayerNameNotSetException err) { + Notification.show("Alle Spieler müssen gesetzt sein").addThemeVariants(NotificationVariant.LUMO_ERROR); } }); diff --git a/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java index ed46440..d0521cd 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java @@ -8,7 +8,9 @@ import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.router.Route; -import org.kickerelo.kickerelo.NoSuchPlayerException; +import org.kickerelo.kickerelo.exception.DuplicatePlayerException; +import org.kickerelo.kickerelo.exception.NoSuchPlayerException; +import org.kickerelo.kickerelo.exception.PlayerNameNotSetException; import org.kickerelo.kickerelo.service.KickerEloService; @Route(value = "enter2vs2") @@ -43,7 +45,11 @@ public class Enter2vs2View extends VerticalLayout { eloService.enterResult2vs2(winnerFrontSelect.getValue(), winnerBackSelect.getValue(), loserFrontSelect.getValue(), loserBackSelect.getValue(), loserGoals.getValue().shortValue()); Notification.show("Gespeichert").addThemeVariants(NotificationVariant.LUMO_SUCCESS); } catch (NoSuchPlayerException err) { - Notification.show("Konnte nicht gespeichert werden").addThemeVariants(NotificationVariant.LUMO_ERROR); + Notification.show("Unbekannter Spieler").addThemeVariants(NotificationVariant.LUMO_ERROR); + } catch (DuplicatePlayerException err) { + Notification.show("Alle Spieler müssen paarweise verschieden sein").addThemeVariants(NotificationVariant.LUMO_ERROR); + } catch (PlayerNameNotSetException err) { + Notification.show("Alle Spieler müssen gesetzt sein").addThemeVariants(NotificationVariant.LUMO_ERROR); } }); From fb4b357970381f585fce97865c18d49f22361b1d Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Fri, 7 Feb 2025 09:44:56 +0100 Subject: [PATCH 3/9] Add dummy chart --- pom.xml | 15 ++++++++ .../kickerelo/layout/KickerAppLayout.java | 6 +++- .../repository/SpielerRepository.java | 1 + .../kickerelo/service/KickerEloService.java | 7 +++- .../kickerelo/util/SpielerEloComparator.java | 12 +++++++ .../kickerelo/kickerelo/views/Chart1vs1.java | 23 ++++++++++++ .../kickerelo/views/Graph1vs1View.java | 14 ++++++++ .../kickerelo/views/PlayerListView.java | 36 +++++++++++++++++++ 8 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java create mode 100644 src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java create mode 100644 src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java create mode 100644 src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java diff --git a/pom.xml b/pom.xml index aacfdfd..8a4aacf 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,16 @@ 24.6.4 + + + Vaadin Directory + https://maven.vaadin.com/vaadin-addons + + false + + + + org.springframework.boot @@ -31,6 +41,11 @@ com.vaadin vaadin-spring-boot-starter + + com.github.appreciated + apexcharts + 24.0.1 + org.mariadb.jdbc diff --git a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java index 4b2a430..e77af40 100644 --- a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java +++ b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java @@ -13,6 +13,8 @@ import com.vaadin.flow.router.RouterLink; import com.vaadin.flow.theme.lumo.LumoUtility; import org.kickerelo.kickerelo.views.Enter1vs1View; import org.kickerelo.kickerelo.views.Enter2vs2View; +import org.kickerelo.kickerelo.views.Graph1vs1View; +import org.kickerelo.kickerelo.views.PlayerListView; @Layout public class KickerAppLayout extends AppLayout { @@ -27,8 +29,10 @@ public class KickerAppLayout extends AppLayout { RouterLink enter1vs1 = new RouterLink("1 vs 1", Enter1vs1View.class); RouterLink enter2vs2 = new RouterLink("2 vs 2", Enter2vs2View.class); + RouterLink playerList = new RouterLink("Spielerliste", PlayerListView.class); + RouterLink graph1vs1 = new RouterLink("Graph 1 vs 1", Graph1vs1View.class); - Tabs tabs = new Tabs(new Tab(enter1vs1), new Tab(enter2vs2)); + Tabs tabs = new Tabs(new Tab(enter1vs1), new Tab(enter2vs2), new Tab(playerList), new Tab(graph1vs1)); tabs.setOrientation(Tabs.Orientation.VERTICAL); addToDrawer(tabs); } diff --git a/src/main/java/org/kickerelo/kickerelo/repository/SpielerRepository.java b/src/main/java/org/kickerelo/kickerelo/repository/SpielerRepository.java index 833de58..44e340a 100644 --- a/src/main/java/org/kickerelo/kickerelo/repository/SpielerRepository.java +++ b/src/main/java/org/kickerelo/kickerelo/repository/SpielerRepository.java @@ -4,6 +4,7 @@ import org.kickerelo.kickerelo.data.Spieler; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository diff --git a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java index 73931af..1217b2b 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java @@ -11,6 +11,7 @@ import org.kickerelo.kickerelo.model.ResultInfo2vs2; import org.kickerelo.kickerelo.repository.Ergebnis1vs1Repository; import org.kickerelo.kickerelo.repository.Ergebnis2vs2Repository; import org.kickerelo.kickerelo.repository.SpielerRepository; +import org.kickerelo.kickerelo.util.SpielerEloComparator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -28,7 +29,11 @@ public class KickerEloService { private EloCalculationService eloCalculationService; public List getSpielerNamen() { - return spielerRepository.findAll().stream().map(Spieler::getName).toList(); + return spielerRepository.findAll().stream().map(Spieler::getName).sorted().toList(); + } + + public List getSpielerEntities() { + return spielerRepository.findAll().stream().sorted(new SpielerEloComparator()).toList(); } public void enterResult1vs1(String gewinnerName, String verliererName, diff --git a/src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java b/src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java new file mode 100644 index 0000000..f7801eb --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java @@ -0,0 +1,12 @@ +package org.kickerelo.kickerelo.util; + +import org.kickerelo.kickerelo.data.Spieler; + +import java.util.Comparator; + +public class SpielerEloComparator implements Comparator { + @Override + public int compare(Spieler o1, Spieler o2) { + return Float.compare(o1.getElo(), o2.getElo()); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java new file mode 100644 index 0000000..f4583ab --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java @@ -0,0 +1,23 @@ +package org.kickerelo.kickerelo.views; + +import com.github.appreciated.apexcharts.ApexChartsBuilder; +import com.github.appreciated.apexcharts.config.builder.ChartBuilder; +import com.github.appreciated.apexcharts.config.builder.XAxisBuilder; +import com.github.appreciated.apexcharts.config.builder.YAxisBuilder; +import com.github.appreciated.apexcharts.config.chart.Type; +import com.github.appreciated.apexcharts.config.chart.builder.ZoomBuilder; +import com.github.appreciated.apexcharts.config.chart.zoom.ZoomType; +import com.github.appreciated.apexcharts.helper.Series; + +import java.math.BigDecimal; + +public class Chart1vs1 extends ApexChartsBuilder { + public Chart1vs1() { + withChart(ChartBuilder.get().withType(Type.SCATTER) + .withZoom(ZoomBuilder.get().withEnabled(true).withType(ZoomType.XY).build()).build()) + .withSeries(new Series<>("ELO", + new BigDecimal[]{new BigDecimal(1), new BigDecimal(3)}, + new BigDecimal[]{new BigDecimal(2), new BigDecimal(4)})) + .withXaxis(XAxisBuilder.get().build()).withYaxis(YAxisBuilder.get().build()); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java b/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java new file mode 100644 index 0000000..f188bfa --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java @@ -0,0 +1,14 @@ +package org.kickerelo.kickerelo.views; + +import com.github.appreciated.apexcharts.ApexChartsBuilder; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; + +@Route("graph1vs1") +public class Graph1vs1View extends VerticalLayout { + ApexChartsBuilder chart1vs1 = new Chart1vs1(); + public Graph1vs1View() { + add(chart1vs1.build()); + } + +} diff --git a/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java b/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java new file mode 100644 index 0000000..53c83b4 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java @@ -0,0 +1,36 @@ +package org.kickerelo.kickerelo.views; + +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.GridSortOrder; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.data.provider.SortDirection; +import com.vaadin.flow.router.Route; +import org.kickerelo.kickerelo.data.Spieler; +import org.kickerelo.kickerelo.service.KickerEloService; + +import java.util.List; + +@Route("playerlist") +public class PlayerListView extends VerticalLayout { + public PlayerListView(KickerEloService eloService) { + H2 subheading = new H2("Spielerliste"); + + List players = eloService.getSpielerEntities(); + Grid playerGrid = new Grid<>(Spieler.class); + + playerGrid.setItems(players); + playerGrid.removeColumnByKey("id"); + playerGrid.removeColumnByKey("elo_alt"); + Grid.Column nameColumn = playerGrid.getColumnByKey("name"); + Grid.Column eloColumn = playerGrid.getColumnByKey("elo"); + nameColumn.setHeader("Name"); + eloColumn.setHeader("Elo"); + + playerGrid.setColumnOrder(nameColumn, eloColumn); + + GridSortOrder sortOrder = new GridSortOrder<>(eloColumn, SortDirection.DESCENDING); + playerGrid.sort(List.of(sortOrder)); + add(subheading, playerGrid); + } +} From 9f6589d8d286133048b492401f54d37df1f9c6cb Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Fri, 7 Feb 2025 14:59:11 +0100 Subject: [PATCH 4/9] Add 2 vs 2 ELO --- .../org/kickerelo/kickerelo/data/Spieler.java | 23 ++++++++++++++----- .../kickerelo/data/update-schema.sql | 3 ++- .../kickerelo/service/KickerEloService.java | 18 +++++++-------- .../kickerelo/util/SpielerEloComparator.java | 2 +- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/kickerelo/kickerelo/data/Spieler.java b/src/main/java/org/kickerelo/kickerelo/data/Spieler.java index a0f64b1..e6ad958 100644 --- a/src/main/java/org/kickerelo/kickerelo/data/Spieler.java +++ b/src/main/java/org/kickerelo/kickerelo/data/Spieler.java @@ -13,8 +13,11 @@ public class Spieler { @Column(name = "NAME", nullable = false, unique = true) private String name; - @Column(name = "ELO", nullable = false) - private float elo; + @Column(name = "ELO1vs1", nullable = false) + private float elo1vs1; + + @Column(name = "ELO2vs2", nullable = false) + private float elo2vs2; @Column(name = "ELO_ALT") private float elo_alt; @@ -38,12 +41,20 @@ public class Spieler { this.name = name; } - public float getElo() { - return elo; + public float getElo1vs1() { + return elo1vs1; } - public void setElo(float elo) { - this.elo = elo; + public void setElo1vs1(float elo) { + this.elo1vs1 = elo; + } + + public float getElo2vs2() { + return elo2vs2; + } + + public void setElo2vs2(float elo2vs2) { + this.elo2vs2 = elo2vs2; } public float getElo_alt() { diff --git a/src/main/java/org/kickerelo/kickerelo/data/update-schema.sql b/src/main/java/org/kickerelo/kickerelo/data/update-schema.sql index 771b9d4..c056b5d 100644 --- a/src/main/java/org/kickerelo/kickerelo/data/update-schema.sql +++ b/src/main/java/org/kickerelo/kickerelo/data/update-schema.sql @@ -30,7 +30,8 @@ CREATE TABLE spieler ( id INT NOT NULL, name VARCHAR(255) NOT NULL, - elo FLOAT NOT NULL, + elo1vs1 FLOAT NOT NULL, + elo2vs2 FLOAT NOT NULL, elo_alt FLOAT NULL, CONSTRAINT pk_spieler PRIMARY KEY (id) ); diff --git a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java index 1217b2b..e195091 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java @@ -57,11 +57,11 @@ public class KickerEloService { ergebnis1vs1Repository.save(ergebnis); - ResultInfo1vs1 result = new ResultInfo1vs1(gewinner.getElo(), ergebnis.getToreVerlierer(), verlierer.getElo()); + ResultInfo1vs1 result = new ResultInfo1vs1(gewinner.getElo1vs1(), ergebnis.getToreVerlierer(), verlierer.getElo1vs1()); eloCalculationService.updateElo1vs1(result); - gewinner.setElo(result.getNewEloWinner()); + gewinner.setElo1vs1(result.getNewEloWinner()); spielerRepository.save(gewinner); - verlierer.setElo(result.getNewEloLoser()); + verlierer.setElo1vs1(result.getNewEloLoser()); spielerRepository.save(verlierer); } @@ -100,22 +100,22 @@ public class KickerEloService { ergebnis2vs2Repository.save(ergebnis); - ResultInfo2vs2 result = new ResultInfo2vs2(gewinnerVorn.getElo(), gewinnerHinten.getElo(), verliererVorn.getElo(), verliererHinten.getElo(), toreVerlierer); + ResultInfo2vs2 result = new ResultInfo2vs2(gewinnerVorn.getElo2vs2(), gewinnerHinten.getElo2vs2(), verliererVorn.getElo2vs2(), verliererHinten.getElo2vs2(), toreVerlierer); eloCalculationService.updateElo2vs2(result); - gewinnerVorn.setElo(result.getNewEloWinnerFront()); + gewinnerVorn.setElo2vs2(result.getNewEloWinnerFront()); spielerRepository.save(gewinnerVorn); - gewinnerHinten.setElo(result.getNewEloWinnerBack()); + gewinnerHinten.setElo2vs2(result.getNewEloWinnerBack()); spielerRepository.save(gewinnerHinten); - verliererVorn.setElo(result.getNewEloLoserFront()); + verliererVorn.setElo2vs2(result.getNewEloLoserFront()); spielerRepository.save(verliererVorn); - verliererHinten.setElo(result.getNewEloLoserBack()); + verliererHinten.setElo2vs2(result.getNewEloLoserBack()); spielerRepository.save(verliererHinten); } public void addSpieler(String name) { Spieler spieler = new Spieler(); spieler.setName(name); - spieler.setElo(1500); + spieler.setElo1vs1(1500); spielerRepository.save(spieler); } } diff --git a/src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java b/src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java index f7801eb..19e7d6d 100644 --- a/src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java +++ b/src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java @@ -7,6 +7,6 @@ import java.util.Comparator; public class SpielerEloComparator implements Comparator { @Override public int compare(Spieler o1, Spieler o2) { - return Float.compare(o1.getElo(), o2.getElo()); + return Float.compare(o1.getElo1vs1(), o2.getElo1vs1()); } } From f25c9a33a203b535730804045ef5bd82e0631db3 Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Fri, 7 Feb 2025 15:38:48 +0100 Subject: [PATCH 5/9] Add working ELO graphs --- .../kickerelo/layout/KickerAppLayout.java | 10 +++---- .../kickerelo/service/KickerEloService.java | 4 +-- ...tor.java => Spieler1vs1EloComparator.java} | 2 +- .../util/Spieler2vs2EloComparator.java | 12 +++++++++ .../kickerelo/kickerelo/views/Chart1vs1.java | 12 ++++++--- .../kickerelo/kickerelo/views/Chart2vs2.java | 26 +++++++++++++++++++ .../kickerelo/views/Graph1vs1View.java | 12 ++++++--- .../kickerelo/views/Graph2vs2View.java | 17 ++++++++++++ .../kickerelo/views/PlayerListView.java | 9 ++++--- 9 files changed, 85 insertions(+), 19 deletions(-) rename src/main/java/org/kickerelo/kickerelo/util/{SpielerEloComparator.java => Spieler1vs1EloComparator.java} (77%) create mode 100644 src/main/java/org/kickerelo/kickerelo/util/Spieler2vs2EloComparator.java create mode 100644 src/main/java/org/kickerelo/kickerelo/views/Chart2vs2.java create mode 100644 src/main/java/org/kickerelo/kickerelo/views/Graph2vs2View.java diff --git a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java index e77af40..78162ff 100644 --- a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java +++ b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java @@ -11,10 +11,7 @@ import com.vaadin.flow.router.Layout; import com.vaadin.flow.router.Route; import com.vaadin.flow.router.RouterLink; import com.vaadin.flow.theme.lumo.LumoUtility; -import org.kickerelo.kickerelo.views.Enter1vs1View; -import org.kickerelo.kickerelo.views.Enter2vs2View; -import org.kickerelo.kickerelo.views.Graph1vs1View; -import org.kickerelo.kickerelo.views.PlayerListView; +import org.kickerelo.kickerelo.views.*; @Layout public class KickerAppLayout extends AppLayout { @@ -31,8 +28,11 @@ public class KickerAppLayout extends AppLayout { RouterLink enter2vs2 = new RouterLink("2 vs 2", Enter2vs2View.class); RouterLink playerList = new RouterLink("Spielerliste", PlayerListView.class); RouterLink graph1vs1 = new RouterLink("Graph 1 vs 1", Graph1vs1View.class); + RouterLink graph2vs2 = new RouterLink("Graph 2 vs 2", Graph2vs2View.class); - Tabs tabs = new Tabs(new Tab(enter1vs1), new Tab(enter2vs2), new Tab(playerList), new Tab(graph1vs1)); + + + Tabs tabs = new Tabs(new Tab(enter1vs1), new Tab(enter2vs2), new Tab(playerList), new Tab(graph1vs1), new Tab(graph2vs2)); tabs.setOrientation(Tabs.Orientation.VERTICAL); addToDrawer(tabs); } diff --git a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java index e195091..0c0b189 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java @@ -11,7 +11,7 @@ import org.kickerelo.kickerelo.model.ResultInfo2vs2; import org.kickerelo.kickerelo.repository.Ergebnis1vs1Repository; import org.kickerelo.kickerelo.repository.Ergebnis2vs2Repository; import org.kickerelo.kickerelo.repository.SpielerRepository; -import org.kickerelo.kickerelo.util.SpielerEloComparator; +import org.kickerelo.kickerelo.util.Spieler1vs1EloComparator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -33,7 +33,7 @@ public class KickerEloService { } public List getSpielerEntities() { - return spielerRepository.findAll().stream().sorted(new SpielerEloComparator()).toList(); + return spielerRepository.findAll().stream().sorted(new Spieler1vs1EloComparator()).toList(); } public void enterResult1vs1(String gewinnerName, String verliererName, diff --git a/src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java b/src/main/java/org/kickerelo/kickerelo/util/Spieler1vs1EloComparator.java similarity index 77% rename from src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java rename to src/main/java/org/kickerelo/kickerelo/util/Spieler1vs1EloComparator.java index 19e7d6d..57254fe 100644 --- a/src/main/java/org/kickerelo/kickerelo/util/SpielerEloComparator.java +++ b/src/main/java/org/kickerelo/kickerelo/util/Spieler1vs1EloComparator.java @@ -4,7 +4,7 @@ import org.kickerelo.kickerelo.data.Spieler; import java.util.Comparator; -public class SpielerEloComparator implements Comparator { +public class Spieler1vs1EloComparator implements Comparator { @Override public int compare(Spieler o1, Spieler o2) { return Float.compare(o1.getElo1vs1(), o2.getElo1vs1()); diff --git a/src/main/java/org/kickerelo/kickerelo/util/Spieler2vs2EloComparator.java b/src/main/java/org/kickerelo/kickerelo/util/Spieler2vs2EloComparator.java new file mode 100644 index 0000000..3886b2a --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/util/Spieler2vs2EloComparator.java @@ -0,0 +1,12 @@ +package org.kickerelo.kickerelo.util; + +import org.kickerelo.kickerelo.data.Spieler; + +import java.util.Comparator; + +public class Spieler2vs2EloComparator implements Comparator { + @Override + public int compare(Spieler o1, Spieler o2) { + return Float.compare(o1.getElo2vs2(), o2.getElo2vs2()); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java index f4583ab..f3985dc 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java @@ -8,16 +8,20 @@ import com.github.appreciated.apexcharts.config.chart.Type; import com.github.appreciated.apexcharts.config.chart.builder.ZoomBuilder; import com.github.appreciated.apexcharts.config.chart.zoom.ZoomType; import com.github.appreciated.apexcharts.helper.Series; +import org.kickerelo.kickerelo.data.Spieler; +import org.kickerelo.kickerelo.util.Spieler1vs1EloComparator; import java.math.BigDecimal; +import java.util.List; public class Chart1vs1 extends ApexChartsBuilder { - public Chart1vs1() { + public Chart1vs1(List l) { withChart(ChartBuilder.get().withType(Type.SCATTER) .withZoom(ZoomBuilder.get().withEnabled(true).withType(ZoomType.XY).build()).build()) .withSeries(new Series<>("ELO", - new BigDecimal[]{new BigDecimal(1), new BigDecimal(3)}, - new BigDecimal[]{new BigDecimal(2), new BigDecimal(4)})) - .withXaxis(XAxisBuilder.get().build()).withYaxis(YAxisBuilder.get().build()); + l.stream().sorted(new Spieler1vs1EloComparator()).map(Spieler::getElo1vs1).toArray() + )) + .withXaxis(XAxisBuilder.get().withCategories(l.stream().sorted(new Spieler1vs1EloComparator()).map(Spieler::getName).toList()).build()) + .withYaxis(YAxisBuilder.get().build()); } } diff --git a/src/main/java/org/kickerelo/kickerelo/views/Chart2vs2.java b/src/main/java/org/kickerelo/kickerelo/views/Chart2vs2.java new file mode 100644 index 0000000..8de13b8 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/Chart2vs2.java @@ -0,0 +1,26 @@ +package org.kickerelo.kickerelo.views; + +import com.github.appreciated.apexcharts.ApexChartsBuilder; +import com.github.appreciated.apexcharts.config.builder.ChartBuilder; +import com.github.appreciated.apexcharts.config.builder.XAxisBuilder; +import com.github.appreciated.apexcharts.config.builder.YAxisBuilder; +import com.github.appreciated.apexcharts.config.chart.Type; +import com.github.appreciated.apexcharts.config.chart.builder.ZoomBuilder; +import com.github.appreciated.apexcharts.config.chart.zoom.ZoomType; +import com.github.appreciated.apexcharts.helper.Series; +import org.kickerelo.kickerelo.data.Spieler; +import org.kickerelo.kickerelo.util.Spieler2vs2EloComparator; + +import java.util.List; + +public class Chart2vs2 extends ApexChartsBuilder { + public Chart2vs2(List l) { + withChart(ChartBuilder.get().withType(Type.SCATTER) + .withZoom(ZoomBuilder.get().withEnabled(true).withType(ZoomType.XY).build()).build()) + .withSeries(new Series<>("ELO", + l.stream().sorted(new Spieler2vs2EloComparator()).map(Spieler::getElo2vs2).toArray() + )) + .withXaxis(XAxisBuilder.get().withCategories(l.stream().sorted(new Spieler2vs2EloComparator()).map(Spieler::getName).toList()).build()) + .withYaxis(YAxisBuilder.get().build()); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java b/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java index f188bfa..73d7136 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java @@ -1,14 +1,20 @@ package org.kickerelo.kickerelo.views; import com.github.appreciated.apexcharts.ApexChartsBuilder; +import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.Route; +import org.kickerelo.kickerelo.service.KickerEloService; +import org.springframework.beans.factory.annotation.Autowired; @Route("graph1vs1") public class Graph1vs1View extends VerticalLayout { - ApexChartsBuilder chart1vs1 = new Chart1vs1(); - public Graph1vs1View() { - add(chart1vs1.build()); + + ApexChartsBuilder chart1vs1; + public Graph1vs1View(KickerEloService service) { + H2 subheading = new H2("1 vs 1 Elo"); + chart1vs1 = new Chart1vs1(service.getSpielerEntities()); + add(chart1vs1.build(), subheading); } } diff --git a/src/main/java/org/kickerelo/kickerelo/views/Graph2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/Graph2vs2View.java new file mode 100644 index 0000000..46a5107 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/Graph2vs2View.java @@ -0,0 +1,17 @@ +package org.kickerelo.kickerelo.views; + +import com.github.appreciated.apexcharts.ApexChartsBuilder; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import org.kickerelo.kickerelo.service.KickerEloService; + +@Route("graph2vs2") +public class Graph2vs2View extends VerticalLayout { + ApexChartsBuilder chart2vs2; + public Graph2vs2View(KickerEloService service) { + H2 subheading = new H2("2 vs 2 Elo"); + chart2vs2 = new Chart2vs2(service.getSpielerEntities()); + add(chart2vs2.build(), subheading); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java b/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java index 53c83b4..1fd8d79 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java +++ b/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java @@ -23,13 +23,14 @@ public class PlayerListView extends VerticalLayout { playerGrid.removeColumnByKey("id"); playerGrid.removeColumnByKey("elo_alt"); Grid.Column nameColumn = playerGrid.getColumnByKey("name"); - Grid.Column eloColumn = playerGrid.getColumnByKey("elo"); + Grid.Column elo1vs1Column = playerGrid.getColumnByKey("elo1vs1"); + Grid.Column elo2vs2Column = playerGrid.getColumnByKey("elo2vs2"); nameColumn.setHeader("Name"); - eloColumn.setHeader("Elo"); + elo1vs1Column.setHeader("Elo 1 vs 1"); - playerGrid.setColumnOrder(nameColumn, eloColumn); + playerGrid.setColumnOrder(nameColumn, elo1vs1Column, elo2vs2Column); - GridSortOrder sortOrder = new GridSortOrder<>(eloColumn, SortDirection.DESCENDING); + GridSortOrder sortOrder = new GridSortOrder<>(elo1vs1Column, SortDirection.DESCENDING); playerGrid.sort(List.of(sortOrder)); add(subheading, playerGrid); } From ff17b0444ca9eda9c264ae6bcd195744e48da023 Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Fri, 7 Feb 2025 17:11:15 +0100 Subject: [PATCH 6/9] Add admin panel, input checks --- .../exception/InvalidDataException.java | 7 ++ .../kickerelo/layout/KickerAppLayout.java | 3 +- .../kickerelo/model/ResultInfo1vs1.java | 55 ----------- .../kickerelo/model/ResultInfo2vs2.java | 93 ------------------- .../repository/Ergebnis1vs1Repository.java | 3 + .../repository/Ergebnis2vs2Repository.java | 2 + .../service/EloCalculationService.java | 32 +++++-- .../kickerelo/service/KickerEloService.java | 67 ++++++++++--- .../util/Ergebnis1vs1TimeComparator.java | 12 +++ .../util/Ergebnis2vs2TimeComparator.java | 12 +++ .../util/Spieler1vs1EloComparator.java | 2 +- .../util/Spieler2vs2EloComparator.java | 2 +- .../kickerelo/kickerelo/views/AdminView.java | 50 ++++++++++ .../kickerelo/kickerelo/views/Chart1vs1.java | 1 + .../kickerelo/views/Enter1vs1View.java | 3 + .../kickerelo/views/Enter2vs2View.java | 3 + .../kickerelo/views/Graph1vs1View.java | 2 +- .../kickerelo/views/Graph2vs2View.java | 2 +- 18 files changed, 175 insertions(+), 176 deletions(-) create mode 100644 src/main/java/org/kickerelo/kickerelo/exception/InvalidDataException.java delete mode 100644 src/main/java/org/kickerelo/kickerelo/model/ResultInfo1vs1.java delete mode 100644 src/main/java/org/kickerelo/kickerelo/model/ResultInfo2vs2.java create mode 100644 src/main/java/org/kickerelo/kickerelo/util/Ergebnis1vs1TimeComparator.java create mode 100644 src/main/java/org/kickerelo/kickerelo/util/Ergebnis2vs2TimeComparator.java create mode 100644 src/main/java/org/kickerelo/kickerelo/views/AdminView.java diff --git a/src/main/java/org/kickerelo/kickerelo/exception/InvalidDataException.java b/src/main/java/org/kickerelo/kickerelo/exception/InvalidDataException.java new file mode 100644 index 0000000..b0163ef --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/exception/InvalidDataException.java @@ -0,0 +1,7 @@ +package org.kickerelo.kickerelo.exception; + +public class InvalidDataException extends RuntimeException { + public InvalidDataException(String message) { + super(message); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java index 78162ff..16218bd 100644 --- a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java +++ b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java @@ -29,10 +29,11 @@ public class KickerAppLayout extends AppLayout { RouterLink playerList = new RouterLink("Spielerliste", PlayerListView.class); RouterLink graph1vs1 = new RouterLink("Graph 1 vs 1", Graph1vs1View.class); RouterLink graph2vs2 = new RouterLink("Graph 2 vs 2", Graph2vs2View.class); + RouterLink admin = new RouterLink("Verwaltung", AdminView.class); - Tabs tabs = new Tabs(new Tab(enter1vs1), new Tab(enter2vs2), new Tab(playerList), new Tab(graph1vs1), new Tab(graph2vs2)); + Tabs tabs = new Tabs(new Tab(enter1vs1), new Tab(enter2vs2), new Tab(playerList), new Tab(graph1vs1), new Tab(graph2vs2), new Tab(admin)); tabs.setOrientation(Tabs.Orientation.VERTICAL); addToDrawer(tabs); } diff --git a/src/main/java/org/kickerelo/kickerelo/model/ResultInfo1vs1.java b/src/main/java/org/kickerelo/kickerelo/model/ResultInfo1vs1.java deleted file mode 100644 index 043f61c..0000000 --- a/src/main/java/org/kickerelo/kickerelo/model/ResultInfo1vs1.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.kickerelo.kickerelo.model; - -public class ResultInfo1vs1 { - float oldEloWinner; - float oldEloLoser; - float newEloWinner; - float newEloLoser; - short loserGoals; - - public ResultInfo1vs1(float oldEloWinner, short loserGoals, float oldEloLoser) { - this.oldEloWinner = oldEloWinner; - this.loserGoals = loserGoals; - this.oldEloLoser = oldEloLoser; - } - - public float getOldEloWinner() { - return oldEloWinner; - } - - public void setOldEloWinner(float oldEloWinner) { - this.oldEloWinner = oldEloWinner; - } - - public float getOldEloLoser() { - return oldEloLoser; - } - - public void setOldEloLoser(float oldEloLoser) { - this.oldEloLoser = oldEloLoser; - } - - public float getNewEloWinner() { - return newEloWinner; - } - - public void setNewEloWinner(float newEloWinner) { - this.newEloWinner = newEloWinner; - } - - public float getNewEloLoser() { - return newEloLoser; - } - - public void setNewEloLoser(float newEloLoser) { - this.newEloLoser = newEloLoser; - } - - public short getLoserGoals() { - return loserGoals; - } - - public void setLoserGoals(short loserGoals) { - this.loserGoals = loserGoals; - } -} diff --git a/src/main/java/org/kickerelo/kickerelo/model/ResultInfo2vs2.java b/src/main/java/org/kickerelo/kickerelo/model/ResultInfo2vs2.java deleted file mode 100644 index 96b5edc..0000000 --- a/src/main/java/org/kickerelo/kickerelo/model/ResultInfo2vs2.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.kickerelo.kickerelo.model; - -public class ResultInfo2vs2 { - float oldEloWinnerFront; - float oldEloWinnerBack; - float oldEloLoserFront; - float oldEloLoserBack; - float newEloWinnerFront; - float newEloWinnerBack; - float newEloLoserFront; - float newEloLoserBack; - short loserGoals; - - public ResultInfo2vs2(float oldEloWinnerFront, float oldEloWinnerBack, float oldEloLoserFront, float oldEloLoserBack, short loserGoals) { - this.oldEloWinnerFront = oldEloWinnerFront; - this.oldEloWinnerBack = oldEloWinnerBack; - this.oldEloLoserFront = oldEloLoserFront; - this.oldEloLoserBack = oldEloLoserBack; - this.loserGoals = loserGoals; - } - - public float getOldEloWinnerFront() { - return oldEloWinnerFront; - } - - public void setOldEloWinnerFront(float oldEloWinnerFront) { - this.oldEloWinnerFront = oldEloWinnerFront; - } - - public float getOldEloWinnerBack() { - return oldEloWinnerBack; - } - - public void setOldEloWinnerBack(float oldEloWinnerBack) { - this.oldEloWinnerBack = oldEloWinnerBack; - } - - public float getOldEloLoserFront() { - return oldEloLoserFront; - } - - public void setOldEloLoserFront(float oldEloLoserFront) { - this.oldEloLoserFront = oldEloLoserFront; - } - - public float getOldEloLoserBack() { - return oldEloLoserBack; - } - - public void setOldEloLoserBack(float oldEloLoserBack) { - this.oldEloLoserBack = oldEloLoserBack; - } - - public float getNewEloWinnerFront() { - return newEloWinnerFront; - } - - public void setNewEloWinnerFront(float newEloWinnerFront) { - this.newEloWinnerFront = newEloWinnerFront; - } - - public float getNewEloWinnerBack() { - return newEloWinnerBack; - } - - public void setNewEloWinnerBack(float newEloWinnerBack) { - this.newEloWinnerBack = newEloWinnerBack; - } - - public float getNewEloLoserFront() { - return newEloLoserFront; - } - - public void setNewEloLoserFront(float newEloLoserFront) { - this.newEloLoserFront = newEloLoserFront; - } - - public float getNewEloLoserBack() { - return newEloLoserBack; - } - - public void setNewEloLoserBack(float newEloLoserBack) { - this.newEloLoserBack = newEloLoserBack; - } - - public short getLoserGoals() { - return loserGoals; - } - - public void setLoserGoals(short loserGoals) { - this.loserGoals = loserGoals; - } -} diff --git a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis1vs1Repository.java b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis1vs1Repository.java index 6332e2d..ecbaea3 100644 --- a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis1vs1Repository.java +++ b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis1vs1Repository.java @@ -1,5 +1,8 @@ package org.kickerelo.kickerelo.repository; +import java.util.List; +import java.util.stream.Stream; + import org.kickerelo.kickerelo.data.Ergebnis1vs1; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java index 3e9c3f9..9f2e0f7 100644 --- a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java +++ b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java @@ -4,6 +4,8 @@ import org.kickerelo.kickerelo.data.Ergebnis2vs2; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.stream.Stream; + @Repository public interface Ergebnis2vs2Repository extends JpaRepository { } diff --git a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java index 54d1f1d..3a5c1ed 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java @@ -1,20 +1,32 @@ package org.kickerelo.kickerelo.service; -import org.kickerelo.kickerelo.model.ResultInfo1vs1; -import org.kickerelo.kickerelo.model.ResultInfo2vs2; +import org.kickerelo.kickerelo.data.Spieler; import org.springframework.stereotype.Service; + + @Service public class EloCalculationService { - public void updateElo1vs1(ResultInfo1vs1 result) { - result.setNewEloWinner(result.getOldEloWinner() + 1); - result.setNewEloLoser(result.getOldEloLoser() - 1); + final float initialElo1vs1 = 1500; + final float initialElo2vs2 = 1500; + + public void updateElo1vs1(Spieler gewinner, Spieler verlierer, short toreVerlierer) { + gewinner.setElo1vs1(gewinner.getElo1vs1() + 10 - toreVerlierer); + verlierer.setElo1vs1(verlierer.getElo1vs1() - 10 + toreVerlierer); } - public void updateElo2vs2(ResultInfo2vs2 result) { - result.setNewEloWinnerFront(result.getOldEloWinnerFront() + 1); - result.setNewEloWinnerBack(result.getOldEloWinnerBack() + 1); - result.setNewEloLoserFront(result.getOldEloLoserFront() - 1); - result.setNewEloLoserBack(result.getOldEloLoserBack() - 1); + public void updateElo2vs2(Spieler gewinnerVorn, Spieler gewinnerHinten, Spieler verliererVorn, Spieler verliererHinten, short toreVerlierer) { + gewinnerVorn.setElo2vs2(gewinnerVorn.getElo2vs2() + 10 - toreVerlierer); + gewinnerHinten.setElo2vs2(gewinnerHinten.getElo2vs2() + 10 - toreVerlierer); + verliererVorn.setElo2vs2(verliererVorn.getElo2vs2() - 10 + toreVerlierer); + verliererHinten.setElo2vs2(verliererHinten.getElo2vs2()); + } + + public float getInitialElo1vs1() { + return initialElo1vs1; + } + + public float getInitialElo2vs2() { + return initialElo2vs2; } } diff --git a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java index 0c0b189..9cdc2da 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java @@ -1,21 +1,24 @@ package org.kickerelo.kickerelo.service; import org.kickerelo.kickerelo.exception.DuplicatePlayerException; +import org.kickerelo.kickerelo.exception.InvalidDataException; import org.kickerelo.kickerelo.exception.NoSuchPlayerException; import org.kickerelo.kickerelo.data.Ergebnis1vs1; import org.kickerelo.kickerelo.data.Ergebnis2vs2; import org.kickerelo.kickerelo.data.Spieler; import org.kickerelo.kickerelo.exception.PlayerNameNotSetException; -import org.kickerelo.kickerelo.model.ResultInfo1vs1; -import org.kickerelo.kickerelo.model.ResultInfo2vs2; import org.kickerelo.kickerelo.repository.Ergebnis1vs1Repository; import org.kickerelo.kickerelo.repository.Ergebnis2vs2Repository; import org.kickerelo.kickerelo.repository.SpielerRepository; +import org.kickerelo.kickerelo.util.Ergebnis1vs1TimeComparator; +import org.kickerelo.kickerelo.util.Ergebnis2vs2TimeComparator; import org.kickerelo.kickerelo.util.Spieler1vs1EloComparator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.List; +import java.util.stream.Stream; @Service public class KickerEloService { @@ -45,6 +48,9 @@ public class KickerEloService { if (gewinnerName.equals(verliererName)) { throw new DuplicatePlayerException("winner and loser identical"); } + if (toreVerlierer > 9 || toreVerlierer < 0) { + throw new InvalidDataException("too many goals"); + } Spieler gewinner = spielerRepository.findByName(gewinnerName) .orElseThrow(() -> new NoSuchPlayerException(gewinnerName)); @@ -57,11 +63,8 @@ public class KickerEloService { ergebnis1vs1Repository.save(ergebnis); - ResultInfo1vs1 result = new ResultInfo1vs1(gewinner.getElo1vs1(), ergebnis.getToreVerlierer(), verlierer.getElo1vs1()); - eloCalculationService.updateElo1vs1(result); - gewinner.setElo1vs1(result.getNewEloWinner()); + eloCalculationService.updateElo1vs1(gewinner, verlierer, toreVerlierer); spielerRepository.save(gewinner); - verlierer.setElo1vs1(result.getNewEloLoser()); spielerRepository.save(verlierer); } @@ -84,6 +87,10 @@ public class KickerEloService { throw new DuplicatePlayerException("players must not be identical"); } + if (toreVerlierer > 9 || toreVerlierer < 0) { + throw new InvalidDataException("too many loser goals"); + } + Spieler gewinnerVorn = spielerRepository.findByName(gewinnerNameVorn) .orElseThrow(() -> new NoSuchPlayerException(gewinnerNameVorn)); @@ -100,22 +107,56 @@ public class KickerEloService { ergebnis2vs2Repository.save(ergebnis); - ResultInfo2vs2 result = new ResultInfo2vs2(gewinnerVorn.getElo2vs2(), gewinnerHinten.getElo2vs2(), verliererVorn.getElo2vs2(), verliererHinten.getElo2vs2(), toreVerlierer); - eloCalculationService.updateElo2vs2(result); - gewinnerVorn.setElo2vs2(result.getNewEloWinnerFront()); + eloCalculationService.updateElo2vs2(gewinnerVorn, gewinnerHinten, verliererVorn, verliererHinten, toreVerlierer); spielerRepository.save(gewinnerVorn); - gewinnerHinten.setElo2vs2(result.getNewEloWinnerBack()); spielerRepository.save(gewinnerHinten); - verliererVorn.setElo2vs2(result.getNewEloLoserFront()); spielerRepository.save(verliererVorn); - verliererHinten.setElo2vs2(result.getNewEloLoserBack()); spielerRepository.save(verliererHinten); } public void addSpieler(String name) { + if (name == null || name.isBlank()) { + throw new PlayerNameNotSetException("Leerer Name"); + } + if (getSpielerNamen().contains(name)) { + throw new DuplicatePlayerException("players must not be identical"); + } Spieler spieler = new Spieler(); spieler.setName(name); - spieler.setElo1vs1(1500); + spieler.setElo1vs1(eloCalculationService.getInitialElo1vs1()); + spieler.setElo2vs2(eloCalculationService.getInitialElo2vs2()); spielerRepository.save(spieler); } + + public void recalculateAll1vs1() { + HashMap players = new HashMap<>(); + for (Spieler spieler : spielerRepository.findAll()) { + spieler.setElo1vs1(eloCalculationService.getInitialElo1vs1()); + players.put(spieler.getId(), spieler); + } + Stream results = ergebnis1vs1Repository.findAll().stream().sorted(new Ergebnis1vs1TimeComparator()); + results.forEach(r -> { + eloCalculationService.updateElo1vs1(players.get(r.getGewinner().getId()), + players.get(r.getVerlierer().getId()), + r.getToreVerlierer()); + }); + spielerRepository.saveAll(players.values()); + } + + public void recalculateAll2vs2() { + HashMap players = new HashMap<>(); + for (Spieler spieler : spielerRepository.findAll()) { + spieler.setElo2vs2(eloCalculationService.getInitialElo2vs2()); + players.put(spieler.getId(), spieler); + } + Stream results = ergebnis2vs2Repository.findAll().stream().sorted(new Ergebnis2vs2TimeComparator()); + results.forEach(r -> { + eloCalculationService.updateElo2vs2(players.get(r.getGewinnerVorn().getId()), + players.get(r.getGewinnerHinten().getId()), + players.get(r.getVerliererVorn().getId()), + players.get(r.getVerliererHinten().getId()), + r.getToreVerlierer()); + }); + spielerRepository.saveAll(players.values()); + } } diff --git a/src/main/java/org/kickerelo/kickerelo/util/Ergebnis1vs1TimeComparator.java b/src/main/java/org/kickerelo/kickerelo/util/Ergebnis1vs1TimeComparator.java new file mode 100644 index 0000000..660e0cd --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/util/Ergebnis1vs1TimeComparator.java @@ -0,0 +1,12 @@ +package org.kickerelo.kickerelo.util; + +import org.kickerelo.kickerelo.data.Ergebnis1vs1; + +import java.util.Comparator; + +public class Ergebnis1vs1TimeComparator implements Comparator { + @Override + public int compare(Ergebnis1vs1 o1, Ergebnis1vs1 o2) { + return o1.getTimestamp().compareTo(o2.getTimestamp()); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/util/Ergebnis2vs2TimeComparator.java b/src/main/java/org/kickerelo/kickerelo/util/Ergebnis2vs2TimeComparator.java new file mode 100644 index 0000000..ed0ab69 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/util/Ergebnis2vs2TimeComparator.java @@ -0,0 +1,12 @@ +package org.kickerelo.kickerelo.util; + +import org.kickerelo.kickerelo.data.Ergebnis2vs2; + +import java.util.Comparator; + +public class Ergebnis2vs2TimeComparator implements Comparator { + @Override + public int compare(Ergebnis2vs2 o1, Ergebnis2vs2 o2) { + return o1.getTimestamp().compareTo(o2.getTimestamp()); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/util/Spieler1vs1EloComparator.java b/src/main/java/org/kickerelo/kickerelo/util/Spieler1vs1EloComparator.java index 57254fe..b424ffd 100644 --- a/src/main/java/org/kickerelo/kickerelo/util/Spieler1vs1EloComparator.java +++ b/src/main/java/org/kickerelo/kickerelo/util/Spieler1vs1EloComparator.java @@ -7,6 +7,6 @@ import java.util.Comparator; public class Spieler1vs1EloComparator implements Comparator { @Override public int compare(Spieler o1, Spieler o2) { - return Float.compare(o1.getElo1vs1(), o2.getElo1vs1()); + return Float.compare(o2.getElo1vs1(), o1.getElo1vs1()); } } diff --git a/src/main/java/org/kickerelo/kickerelo/util/Spieler2vs2EloComparator.java b/src/main/java/org/kickerelo/kickerelo/util/Spieler2vs2EloComparator.java index 3886b2a..f9ea739 100644 --- a/src/main/java/org/kickerelo/kickerelo/util/Spieler2vs2EloComparator.java +++ b/src/main/java/org/kickerelo/kickerelo/util/Spieler2vs2EloComparator.java @@ -7,6 +7,6 @@ import java.util.Comparator; public class Spieler2vs2EloComparator implements Comparator { @Override public int compare(Spieler o1, Spieler o2) { - return Float.compare(o1.getElo2vs2(), o2.getElo2vs2()); + return Float.compare(o2.getElo2vs2(), o1.getElo2vs2()); } } diff --git a/src/main/java/org/kickerelo/kickerelo/views/AdminView.java b/src/main/java/org/kickerelo/kickerelo/views/AdminView.java new file mode 100644 index 0000000..421beb2 --- /dev/null +++ b/src/main/java/org/kickerelo/kickerelo/views/AdminView.java @@ -0,0 +1,50 @@ +package org.kickerelo.kickerelo.views; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.router.Route; +import org.kickerelo.kickerelo.exception.DuplicatePlayerException; +import org.kickerelo.kickerelo.exception.PlayerNameNotSetException; +import org.kickerelo.kickerelo.service.KickerEloService; + +@Route("admin") +public class AdminView extends VerticalLayout { + public AdminView(KickerEloService service) { + H2 subheader = new H2("Verwaltung"); + + TextField spielername = new TextField("Spielername"); + spielername.addClassName("bordered"); + + // Button click listeners can be defined as lambda expressions + Button addPlayerButton = new Button("Spieler hinzufügen", e -> { + try { + service.addSpieler(spielername.getValue()); + } catch (PlayerNameNotSetException err) { + Notification.show("Spielername darf nicht leer sein").addThemeVariants(NotificationVariant.LUMO_ERROR); + return; + } + catch (DuplicatePlayerException err) { + Notification.show("Spieler existiert bereits").addThemeVariants(NotificationVariant.LUMO_ERROR); + return; + } + Notification.show("Spieler gespeichert").addThemeVariants(NotificationVariant.LUMO_SUCCESS); + }); + + Button recalc1vs1Button = new Button("1 vs 1 Elo neu berechnen", e -> { + Notification.show("Recalculating Elo").addThemeVariants(NotificationVariant.LUMO_WARNING); + service.recalculateAll1vs1(); + Notification.show("Recalculating finished").addThemeVariants(NotificationVariant.LUMO_SUCCESS); + }); + Button recalc2vs2Button = new Button("2 vs 2 Elo neu berechnen", e -> { + Notification.show("Recalculating Elo").addThemeVariants(NotificationVariant.LUMO_WARNING); + service.recalculateAll2vs2(); + Notification.show("Recalculating finished").addThemeVariants(NotificationVariant.LUMO_SUCCESS); + }); + + add(spielername, spielername, addPlayerButton, recalc1vs1Button, recalc2vs2Button); + } +} diff --git a/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java index f3985dc..b81cc79 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java @@ -7,6 +7,7 @@ import com.github.appreciated.apexcharts.config.builder.YAxisBuilder; import com.github.appreciated.apexcharts.config.chart.Type; import com.github.appreciated.apexcharts.config.chart.builder.ZoomBuilder; import com.github.appreciated.apexcharts.config.chart.zoom.ZoomType; +import com.github.appreciated.apexcharts.config.yaxis.Title; import com.github.appreciated.apexcharts.helper.Series; import org.kickerelo.kickerelo.data.Spieler; import org.kickerelo.kickerelo.util.Spieler1vs1EloComparator; diff --git a/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java b/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java index 1ecd23b..53745c9 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Enter1vs1View.java @@ -9,6 +9,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.router.Route; import org.kickerelo.kickerelo.exception.DuplicatePlayerException; +import org.kickerelo.kickerelo.exception.InvalidDataException; import org.kickerelo.kickerelo.exception.NoSuchPlayerException; import org.kickerelo.kickerelo.exception.PlayerNameNotSetException; import org.kickerelo.kickerelo.service.KickerEloService; @@ -43,6 +44,8 @@ public class Enter1vs1View extends VerticalLayout { Notification.show("Alle Spieler müssen paarweise verschieden sein").addThemeVariants(NotificationVariant.LUMO_ERROR); } catch (PlayerNameNotSetException err) { Notification.show("Alle Spieler müssen gesetzt sein").addThemeVariants(NotificationVariant.LUMO_ERROR); + } catch (InvalidDataException err) { + Notification.show("Verliertore falsch").addThemeVariants(NotificationVariant.LUMO_ERROR); } }); diff --git a/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java index d0521cd..e84fa66 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Enter2vs2View.java @@ -9,6 +9,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.router.Route; import org.kickerelo.kickerelo.exception.DuplicatePlayerException; +import org.kickerelo.kickerelo.exception.InvalidDataException; import org.kickerelo.kickerelo.exception.NoSuchPlayerException; import org.kickerelo.kickerelo.exception.PlayerNameNotSetException; import org.kickerelo.kickerelo.service.KickerEloService; @@ -50,6 +51,8 @@ public class Enter2vs2View extends VerticalLayout { Notification.show("Alle Spieler müssen paarweise verschieden sein").addThemeVariants(NotificationVariant.LUMO_ERROR); } catch (PlayerNameNotSetException err) { Notification.show("Alle Spieler müssen gesetzt sein").addThemeVariants(NotificationVariant.LUMO_ERROR); + } catch (InvalidDataException err) { + Notification.show("Verliertore falsch").addThemeVariants(NotificationVariant.LUMO_ERROR); } }); diff --git a/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java b/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java index 73d7136..7f216e4 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Graph1vs1View.java @@ -14,7 +14,7 @@ public class Graph1vs1View extends VerticalLayout { public Graph1vs1View(KickerEloService service) { H2 subheading = new H2("1 vs 1 Elo"); chart1vs1 = new Chart1vs1(service.getSpielerEntities()); - add(chart1vs1.build(), subheading); + add(subheading, chart1vs1.build()); } } diff --git a/src/main/java/org/kickerelo/kickerelo/views/Graph2vs2View.java b/src/main/java/org/kickerelo/kickerelo/views/Graph2vs2View.java index 46a5107..56353ca 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Graph2vs2View.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Graph2vs2View.java @@ -12,6 +12,6 @@ public class Graph2vs2View extends VerticalLayout { public Graph2vs2View(KickerEloService service) { H2 subheading = new H2("2 vs 2 Elo"); chart2vs2 = new Chart2vs2(service.getSpielerEntities()); - add(chart2vs2.build(), subheading); + add(subheading, chart2vs2.build()); } } From b5c2f2f21dff833e3717660e55cb6d4915a7e4d4 Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Fri, 7 Feb 2025 23:45:05 +0100 Subject: [PATCH 7/9] Rotated labels on the graph, and first attempt at dark mode --- src/main/frontend/prefers-color-scheme.js | 12 ++++++++++++ .../kickerelo/kickerelo/layout/KickerAppLayout.java | 2 ++ .../org/kickerelo/kickerelo/views/Chart1vs1.java | 12 +++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/main/frontend/prefers-color-scheme.js diff --git a/src/main/frontend/prefers-color-scheme.js b/src/main/frontend/prefers-color-scheme.js new file mode 100644 index 0000000..61db7da --- /dev/null +++ b/src/main/frontend/prefers-color-scheme.js @@ -0,0 +1,12 @@ +window.applyTheme = () => { + const theme = window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : ""; + document.documentElement.setAttribute("theme", theme); +}; +window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener('change', function () { + window.applyTheme() + }); +window.applyTheme(); \ No newline at end of file diff --git a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java index 16218bd..675edad 100644 --- a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java +++ b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java @@ -2,6 +2,7 @@ package org.kickerelo.kickerelo.layout; import com.vaadin.flow.component.applayout.AppLayout; import com.vaadin.flow.component.applayout.DrawerToggle; +import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.component.html.H1; import com.vaadin.flow.component.orderedlayout.Scroller; import com.vaadin.flow.component.sidenav.SideNav; @@ -14,6 +15,7 @@ import com.vaadin.flow.theme.lumo.LumoUtility; import org.kickerelo.kickerelo.views.*; @Layout +@JsModule("prefers-color-scheme.js") public class KickerAppLayout extends AppLayout { public KickerAppLayout() { diff --git a/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java index b81cc79..d2b772b 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java @@ -7,6 +7,8 @@ import com.github.appreciated.apexcharts.config.builder.YAxisBuilder; import com.github.appreciated.apexcharts.config.chart.Type; import com.github.appreciated.apexcharts.config.chart.builder.ZoomBuilder; import com.github.appreciated.apexcharts.config.chart.zoom.ZoomType; +import com.github.appreciated.apexcharts.config.xaxis.Labels; +import com.github.appreciated.apexcharts.config.xaxis.labels.Style; import com.github.appreciated.apexcharts.config.yaxis.Title; import com.github.appreciated.apexcharts.helper.Series; import org.kickerelo.kickerelo.data.Spieler; @@ -17,12 +19,20 @@ import java.util.List; public class Chart1vs1 extends ApexChartsBuilder { public Chart1vs1(List l) { + Style style = new Style(); + style.setColors(List.of("#80C3FC")); + Labels labels = new Labels(); + labels.setRotate(270d); + labels.setShow(true); + labels.setRotateAlways(false); + labels.setStyle(style); withChart(ChartBuilder.get().withType(Type.SCATTER) .withZoom(ZoomBuilder.get().withEnabled(true).withType(ZoomType.XY).build()).build()) .withSeries(new Series<>("ELO", l.stream().sorted(new Spieler1vs1EloComparator()).map(Spieler::getElo1vs1).toArray() )) - .withXaxis(XAxisBuilder.get().withCategories(l.stream().sorted(new Spieler1vs1EloComparator()).map(Spieler::getName).toList()).build()) + .withXaxis(XAxisBuilder.get().withCategories(l.stream().sorted(new Spieler1vs1EloComparator()) + .map(Spieler::getName).toList()).withLabels(labels).build()) .withYaxis(YAxisBuilder.get().build()); } } From 76bb3ded81347ee52dde13ede120d63a6794c480 Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Thu, 13 Feb 2025 14:51:07 +0100 Subject: [PATCH 8/9] Tuning --- .../org/kickerelo/kickerelo/MainView.java | 79 ------------------- .../kickerelo/layout/KickerAppLayout.java | 8 +- .../repository/Ergebnis1vs1Repository.java | 3 - .../repository/Ergebnis2vs2Repository.java | 2 - .../repository/SpielerRepository.java | 1 - .../service/EloCalculationService.java | 19 ++++- .../kickerelo/service/KickerEloService.java | 42 +++++++++- .../kickerelo/kickerelo/views/AdminView.java | 6 +- .../kickerelo/kickerelo/views/Chart1vs1.java | 20 ++++- .../kickerelo/kickerelo/views/Chart2vs2.java | 19 ++++- .../kickerelo/views/PlayerListView.java | 3 +- src/main/resources/application.properties | 7 +- 12 files changed, 100 insertions(+), 109 deletions(-) delete mode 100644 src/main/java/org/kickerelo/kickerelo/MainView.java diff --git a/src/main/java/org/kickerelo/kickerelo/MainView.java b/src/main/java/org/kickerelo/kickerelo/MainView.java deleted file mode 100644 index 0e030f1..0000000 --- a/src/main/java/org/kickerelo/kickerelo/MainView.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.kickerelo.kickerelo; - -import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.button.ButtonVariant; -import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; -import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.component.textfield.IntegerField; -import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.router.Route; -import org.kickerelo.kickerelo.exception.NoSuchPlayerException; -import org.kickerelo.kickerelo.service.KickerEloService; - -/** - * A sample Vaadin view class. - *

- * To implement a Vaadin view just extend any Vaadin component and use @Route - * annotation to announce it in a URL as a Spring managed bean. - *

- * A new instance of this class is created for every new user and every browser - * tab/window. - *

- * The main view contains a text field for getting the user name and a button - * that shows a greeting message in a notification. - */ -@Route -public class MainView extends VerticalLayout { - - /** - * Construct a new Vaadin view. - */ - - public MainView(KickerEloService eloService) { - - - TextField spielername = new TextField("Spielername"); - spielername.addClassName("bordered"); - - // Button click listeners can be defined as lambda expressions - Button button = new Button("Spieler hinzufügen", e -> { - eloService.addSpieler(spielername.getValue()); - Notification.show("Spieler gespeichert").addThemeVariants(NotificationVariant.LUMO_SUCCESS); - }); - - button.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - - ComboBox winnerSelect = new ComboBox<>("Gewinner"); - winnerSelect.setItems(eloService.getSpielerNamen()); - winnerSelect.setPlaceholder("Spieler auswählen"); - - ComboBox loserSelect = new ComboBox<>("Verlierer"); - loserSelect.setItems(eloService.getSpielerNamen()); - loserSelect.setPlaceholder("Spieler auswählen"); - - IntegerField loserGoals = new IntegerField("Tore des Verlierers"); - loserGoals.setMin(0); - loserGoals.setMax(9); - loserGoals.setValue(0); - loserGoals.setStepButtonsVisible(true); - - Button saveButton = new Button("Speichern", e -> { - try { - eloService.enterResult1vs1(winnerSelect.getValue(), loserSelect.getValue(), loserGoals.getValue().shortValue()); - Notification.show("Gespeichert").addThemeVariants(NotificationVariant.LUMO_SUCCESS); - } catch (NoSuchPlayerException err) { - Notification.show("Konnte nicht gespeichert werden").addThemeVariants(NotificationVariant.LUMO_ERROR); - } - }); - - - - // Use custom CSS classes to apply styling. This is defined in - // styles.css. - addClassName("centered-content"); - - add(spielername, button, winnerSelect, loserSelect, loserGoals, saveButton); - } -} \ No newline at end of file diff --git a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java index 675edad..8751077 100644 --- a/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java +++ b/src/main/java/org/kickerelo/kickerelo/layout/KickerAppLayout.java @@ -4,14 +4,10 @@ import com.vaadin.flow.component.applayout.AppLayout; import com.vaadin.flow.component.applayout.DrawerToggle; import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.component.html.H1; -import com.vaadin.flow.component.orderedlayout.Scroller; -import com.vaadin.flow.component.sidenav.SideNav; import com.vaadin.flow.component.tabs.Tab; import com.vaadin.flow.component.tabs.Tabs; import com.vaadin.flow.router.Layout; -import com.vaadin.flow.router.Route; import com.vaadin.flow.router.RouterLink; -import com.vaadin.flow.theme.lumo.LumoUtility; import org.kickerelo.kickerelo.views.*; @Layout @@ -33,9 +29,7 @@ public class KickerAppLayout extends AppLayout { RouterLink graph2vs2 = new RouterLink("Graph 2 vs 2", Graph2vs2View.class); RouterLink admin = new RouterLink("Verwaltung", AdminView.class); - - - Tabs tabs = new Tabs(new Tab(enter1vs1), new Tab(enter2vs2), new Tab(playerList), new Tab(graph1vs1), new Tab(graph2vs2), new Tab(admin)); + Tabs tabs = new Tabs(new Tab(playerList), new Tab(enter1vs1), new Tab(enter2vs2), new Tab(graph1vs1), new Tab(graph2vs2), new Tab(admin)); tabs.setOrientation(Tabs.Orientation.VERTICAL); addToDrawer(tabs); } diff --git a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis1vs1Repository.java b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis1vs1Repository.java index ecbaea3..6332e2d 100644 --- a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis1vs1Repository.java +++ b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis1vs1Repository.java @@ -1,8 +1,5 @@ package org.kickerelo.kickerelo.repository; -import java.util.List; -import java.util.stream.Stream; - import org.kickerelo.kickerelo.data.Ergebnis1vs1; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java index 9f2e0f7..3e9c3f9 100644 --- a/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java +++ b/src/main/java/org/kickerelo/kickerelo/repository/Ergebnis2vs2Repository.java @@ -4,8 +4,6 @@ import org.kickerelo.kickerelo.data.Ergebnis2vs2; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.stream.Stream; - @Repository public interface Ergebnis2vs2Repository extends JpaRepository { } diff --git a/src/main/java/org/kickerelo/kickerelo/repository/SpielerRepository.java b/src/main/java/org/kickerelo/kickerelo/repository/SpielerRepository.java index 44e340a..833de58 100644 --- a/src/main/java/org/kickerelo/kickerelo/repository/SpielerRepository.java +++ b/src/main/java/org/kickerelo/kickerelo/repository/SpielerRepository.java @@ -4,7 +4,6 @@ import org.kickerelo.kickerelo.data.Spieler; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.List; import java.util.Optional; @Repository diff --git a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java index 3a5c1ed..4e201a7 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java @@ -4,17 +4,34 @@ import org.kickerelo.kickerelo.data.Spieler; import org.springframework.stereotype.Service; - +/** + * This contains the math for calculating ELO only. + */ @Service public class EloCalculationService { + // Default ELOs for a player with 0 games final float initialElo1vs1 = 1500; final float initialElo2vs2 = 1500; + /** + * 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) { gewinner.setElo1vs1(gewinner.getElo1vs1() + 10 - toreVerlierer); verlierer.setElo1vs1(verlierer.getElo1vs1() - 10 + toreVerlierer); } + /** + * Updates the 2 vs 2 ELOs of the players according to the result 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) { gewinnerVorn.setElo2vs2(gewinnerVorn.getElo2vs2() + 10 - toreVerlierer); gewinnerHinten.setElo2vs2(gewinnerHinten.getElo2vs2() + 10 - toreVerlierer); diff --git a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java index 9cdc2da..b6308e7 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/KickerEloService.java @@ -20,6 +20,9 @@ import java.util.HashMap; import java.util.List; import java.util.stream.Stream; +/** + * Provides all functions for the application + */ @Service public class KickerEloService { @Autowired @@ -31,16 +34,29 @@ public class KickerEloService { @Autowired private EloCalculationService eloCalculationService; + /** + * @return List of all player names sorted alphabetically + */ public List getSpielerNamen() { return spielerRepository.findAll().stream().map(Spieler::getName).sorted().toList(); } + /** + * @return List of all player entities sorted by 1 vs 1 ELO + */ public List getSpielerEntities() { return spielerRepository.findAll().stream().sorted(new Spieler1vs1EloComparator()).toList(); } + /** + * Enter a result of a 1 vs 1 game + * @param gewinnerName The name of the winning player + * @param verliererName The name of the losing player + * @param toreVerlierer The number of goals of the loser + */ public void enterResult1vs1(String gewinnerName, String verliererName, short toreVerlierer) { + // Check if the inputs are valid if (gewinnerName == null || verliererName == null) { throw new PlayerNameNotSetException("Alle Namen müssen gesetzt sein"); } @@ -60,7 +76,6 @@ public class KickerEloService { Ergebnis1vs1 ergebnis = new Ergebnis1vs1(gewinner, verlierer, toreVerlierer); - ergebnis1vs1Repository.save(ergebnis); eloCalculationService.updateElo1vs1(gewinner, verlierer, toreVerlierer); @@ -69,10 +84,18 @@ public class KickerEloService { } + /** + * Enter the result of a 2 vs 2 game + * @param gewinnerNameVorn Name of the winning offensive player + * @param gewinnerNameHinten Name of the winning defensive player + * @param verliererNameVorn Name of the losing offensive player + * @param verliererNameHinten Name of the losing defensive player + * @param toreVerlierer Number of goals of the losing team + */ public void enterResult2vs2(String gewinnerNameVorn, String gewinnerNameHinten, String verliererNameVorn, String verliererNameHinten, short toreVerlierer) { - + // Check if the inputs are valid if (gewinnerNameVorn == null || gewinnerNameHinten == null || verliererNameVorn == null || verliererNameHinten == null) { throw new PlayerNameNotSetException("Alle Namen müssen gesetzt sein"); @@ -104,7 +127,6 @@ public class KickerEloService { .orElseThrow(() -> new NoSuchPlayerException(verliererNameHinten)); Ergebnis2vs2 ergebnis = new Ergebnis2vs2(gewinnerVorn, gewinnerHinten, verliererVorn, verliererHinten, toreVerlierer); - ergebnis2vs2Repository.save(ergebnis); eloCalculationService.updateElo2vs2(gewinnerVorn, gewinnerHinten, verliererVorn, verliererHinten, toreVerlierer); @@ -114,13 +136,21 @@ public class KickerEloService { spielerRepository.save(verliererHinten); } + /** + * Add a new player to the system + * @param name Name of the new player + */ public void addSpieler(String name) { + // Check if the player name is valid if (name == null || name.isBlank()) { throw new PlayerNameNotSetException("Leerer Name"); } if (getSpielerNamen().contains(name)) { throw new DuplicatePlayerException("players must not be identical"); } + if (name.length() > 30) { + throw new InvalidDataException("Zu lang"); + } Spieler spieler = new Spieler(); spieler.setName(name); spieler.setElo1vs1(eloCalculationService.getInitialElo1vs1()); @@ -128,6 +158,9 @@ public class KickerEloService { spielerRepository.save(spieler); } + /** + * Recalculate and overwrite all 1 vs 1 ELOs. + */ public void recalculateAll1vs1() { HashMap players = new HashMap<>(); for (Spieler spieler : spielerRepository.findAll()) { @@ -143,6 +176,9 @@ public class KickerEloService { spielerRepository.saveAll(players.values()); } + /** + * Recalculate and overwrite all 2 vs 2 ELOs. + */ public void recalculateAll2vs2() { HashMap players = new HashMap<>(); for (Spieler spieler : spielerRepository.findAll()) { diff --git a/src/main/java/org/kickerelo/kickerelo/views/AdminView.java b/src/main/java/org/kickerelo/kickerelo/views/AdminView.java index 421beb2..5098cbb 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/AdminView.java +++ b/src/main/java/org/kickerelo/kickerelo/views/AdminView.java @@ -8,6 +8,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.router.Route; import org.kickerelo.kickerelo.exception.DuplicatePlayerException; +import org.kickerelo.kickerelo.exception.InvalidDataException; import org.kickerelo.kickerelo.exception.PlayerNameNotSetException; import org.kickerelo.kickerelo.service.KickerEloService; @@ -26,10 +27,11 @@ public class AdminView extends VerticalLayout { } catch (PlayerNameNotSetException err) { Notification.show("Spielername darf nicht leer sein").addThemeVariants(NotificationVariant.LUMO_ERROR); return; - } - catch (DuplicatePlayerException err) { + } catch (DuplicatePlayerException err) { Notification.show("Spieler existiert bereits").addThemeVariants(NotificationVariant.LUMO_ERROR); return; + } catch (InvalidDataException err) { + Notification.show("Name zu lang").addThemeVariants(NotificationVariant.LUMO_ERROR); } Notification.show("Spieler gespeichert").addThemeVariants(NotificationVariant.LUMO_SUCCESS); }); diff --git a/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java index d2b772b..48e7df3 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Chart1vs1.java @@ -1,16 +1,23 @@ package org.kickerelo.kickerelo.views; import com.github.appreciated.apexcharts.ApexChartsBuilder; +import com.github.appreciated.apexcharts.config.Theme; import com.github.appreciated.apexcharts.config.builder.ChartBuilder; import com.github.appreciated.apexcharts.config.builder.XAxisBuilder; import com.github.appreciated.apexcharts.config.builder.YAxisBuilder; import com.github.appreciated.apexcharts.config.chart.Type; import com.github.appreciated.apexcharts.config.chart.builder.ZoomBuilder; import com.github.appreciated.apexcharts.config.chart.zoom.ZoomType; +import com.github.appreciated.apexcharts.config.theme.Mode; +import com.github.appreciated.apexcharts.config.theme.Monochrome; import com.github.appreciated.apexcharts.config.xaxis.Labels; import com.github.appreciated.apexcharts.config.xaxis.labels.Style; import com.github.appreciated.apexcharts.config.yaxis.Title; import com.github.appreciated.apexcharts.helper.Series; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.server.VaadinService; +import com.vaadin.flow.theme.lumo.Lumo; import org.kickerelo.kickerelo.data.Spieler; import org.kickerelo.kickerelo.util.Spieler1vs1EloComparator; @@ -19,13 +26,17 @@ import java.util.List; public class Chart1vs1 extends ApexChartsBuilder { public Chart1vs1(List l) { - Style style = new Style(); - style.setColors(List.of("#80C3FC")); + Theme theme = new Theme(); + Monochrome monochrome = new Monochrome(); + monochrome.setEnabled(true); + theme.setMode(Mode.DARK); + theme.setMonochrome(monochrome); Labels labels = new Labels(); labels.setRotate(270d); labels.setShow(true); labels.setRotateAlways(false); - labels.setStyle(style); + + withChart(ChartBuilder.get().withType(Type.SCATTER) .withZoom(ZoomBuilder.get().withEnabled(true).withType(ZoomType.XY).build()).build()) .withSeries(new Series<>("ELO", @@ -33,6 +44,7 @@ public class Chart1vs1 extends ApexChartsBuilder { )) .withXaxis(XAxisBuilder.get().withCategories(l.stream().sorted(new Spieler1vs1EloComparator()) .map(Spieler::getName).toList()).withLabels(labels).build()) - .withYaxis(YAxisBuilder.get().build()); + .withYaxis(YAxisBuilder.get().build()) + .withTheme(theme); } } diff --git a/src/main/java/org/kickerelo/kickerelo/views/Chart2vs2.java b/src/main/java/org/kickerelo/kickerelo/views/Chart2vs2.java index 8de13b8..696e0cd 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/Chart2vs2.java +++ b/src/main/java/org/kickerelo/kickerelo/views/Chart2vs2.java @@ -1,12 +1,16 @@ package org.kickerelo.kickerelo.views; import com.github.appreciated.apexcharts.ApexChartsBuilder; +import com.github.appreciated.apexcharts.config.Theme; import com.github.appreciated.apexcharts.config.builder.ChartBuilder; import com.github.appreciated.apexcharts.config.builder.XAxisBuilder; import com.github.appreciated.apexcharts.config.builder.YAxisBuilder; import com.github.appreciated.apexcharts.config.chart.Type; import com.github.appreciated.apexcharts.config.chart.builder.ZoomBuilder; import com.github.appreciated.apexcharts.config.chart.zoom.ZoomType; +import com.github.appreciated.apexcharts.config.theme.Mode; +import com.github.appreciated.apexcharts.config.theme.Monochrome; +import com.github.appreciated.apexcharts.config.xaxis.Labels; import com.github.appreciated.apexcharts.helper.Series; import org.kickerelo.kickerelo.data.Spieler; import org.kickerelo.kickerelo.util.Spieler2vs2EloComparator; @@ -15,12 +19,23 @@ import java.util.List; public class Chart2vs2 extends ApexChartsBuilder { public Chart2vs2(List l) { + Theme theme = new Theme(); + Monochrome monochrome = new Monochrome(); + monochrome.setEnabled(true); + theme.setMode(Mode.DARK); + theme.setMonochrome(monochrome); + Labels labels = new Labels(); + labels.setRotate(270d); + labels.setShow(true); + labels.setRotateAlways(false); + withChart(ChartBuilder.get().withType(Type.SCATTER) .withZoom(ZoomBuilder.get().withEnabled(true).withType(ZoomType.XY).build()).build()) .withSeries(new Series<>("ELO", l.stream().sorted(new Spieler2vs2EloComparator()).map(Spieler::getElo2vs2).toArray() )) - .withXaxis(XAxisBuilder.get().withCategories(l.stream().sorted(new Spieler2vs2EloComparator()).map(Spieler::getName).toList()).build()) - .withYaxis(YAxisBuilder.get().build()); + .withXaxis(XAxisBuilder.get().withCategories(l.stream().sorted(new Spieler2vs2EloComparator()).map(Spieler::getName).toList()).withLabels(labels).build()) + .withYaxis(YAxisBuilder.get().build()) + .withTheme(theme); } } diff --git a/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java b/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java index 1fd8d79..4be1c89 100644 --- a/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java +++ b/src/main/java/org/kickerelo/kickerelo/views/PlayerListView.java @@ -11,9 +11,10 @@ import org.kickerelo.kickerelo.service.KickerEloService; import java.util.List; -@Route("playerlist") +@Route("") public class PlayerListView extends VerticalLayout { public PlayerListView(KickerEloService eloService) { + setSizeFull(); H2 subheading = new H2("Spielerliste"); List players = eloService.getSpielerEntities(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 743bb0a..902fa33 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,10 +6,9 @@ spring.mustache.check-template-location = false vaadin.launch-browser=true spring.datasource.url=jdbc:mariadb://localhost:3306/kickerelo -spring.datasource.username= -spring.datasource.password= +spring.datasource.username=root +spring.datasource.password=root spring.datasource.driver-class-name=org.mariadb.jdbc.Driver spring.jpa.hibernate.ddl-auto=validate -spring.jpa.show-sql=true -spring.jpa.format-sql=true +spring.jpa.show-sql=false spring.jpa.open-in-view=false \ No newline at end of file From bffee21de96af8244d8fbe9ea3b86b3a8aefd4c3 Mon Sep 17 00:00:00 2001 From: Anton Micke Date: Fri, 7 Mar 2025 14:58:53 +0100 Subject: [PATCH 9/9] Use actual ELO calculation for 1 vs 1 --- .../service/EloCalculationService.java | 17 ++++++++++++----- 1 file changed, 12 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 4e201a7..b586f3a 100644 --- a/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java +++ b/src/main/java/org/kickerelo/kickerelo/service/EloCalculationService.java @@ -9,9 +9,8 @@ import org.springframework.stereotype.Service; */ @Service public class EloCalculationService { - // Default ELOs for a player with 0 games - final float initialElo1vs1 = 1500; - final float initialElo2vs2 = 1500; + private final float initialElo1vs1 = 1500; + private final float initialElo2vs2 = 1500; /** * Updates the 1 vs 1 ELOs of the players according to the result of the game. @@ -20,8 +19,16 @@ public class EloCalculationService { * @param toreVerlierer The number of goals of the losing player */ public void updateElo1vs1(Spieler gewinner, Spieler verlierer, short toreVerlierer) { - gewinner.setElo1vs1(gewinner.getElo1vs1() + 10 - toreVerlierer); - verlierer.setElo1vs1(verlierer.getElo1vs1() - 10 + toreVerlierer); + final float initialElo = 1500; + 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); } /**