diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f2da7efc2f3c3af36b97c73cf2c132879c66e847 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Score Board Library (simple) +Live Football World Cup Score Board that shows matches and scores + +## Content +This module includes: +- API for the library +- Generic implementation +- Reference implementation + +## Description +The board supports the following operations: +1. Start a game. When a game starts, it should capture (being initial score 0 – 0): + a. Home team + b. Away team +2. Finish game. It will remove a match from the scoreboard. +3. Update score. Receiving the pair score; home team score and away team score + updates a game score. +4. Get a summary of games by total score. Those games with the same total score will + be returned ordered by the most recently added to our system. + +As an example, being the current data in the system: +``` + a. Mexico - Canada: 0 - 5 + b. Spain - Brazil: 10 – 2 + c. Germany - France: 2 – 2 + d. Uruguay - Italy: 6 – 6 + e. Argentina - Australia: 3 - 1 +``` + The summary would provide with the following information: +``` +5. Uruguay 6 - Italy 6 +6. Spain 10 - Brazil 2 +7. Mexico 0 - Canada 5 +8. Argentina 3 - Australia 1 +9. Germany 2 - France 2 +``` diff --git a/pom.xml b/pom.xml index bd6a6c9e9cfa39e6a1612ddd5b4a27329c7c2f19..9a50dcb1722c4b23c6eaa92d9ae83ed43fd519a8 100644 --- a/pom.xml +++ b/pom.xml @@ -22,9 +22,17 @@ junit junit - 4.11 + 4.13.2 test + + + org.mockito + mockito-core + 4.6.1 + test + + @@ -71,5 +79,15 @@ + + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + + diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java deleted file mode 100644 index 5f21d2e226b4d7fdea016d811e98992006c6f8b1..0000000000000000000000000000000000000000 --- a/src/main/java/org/example/App.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.example; - -/** - * Hello world! - * - */ -public class App -{ - public static void main( String[] args ) - { - System.out.println( "Hello World!" ); - } -} diff --git a/src/main/java/org/example/scoreboard/IMatch.java b/src/main/java/org/example/scoreboard/IMatch.java deleted file mode 100644 index 134d5dd1647c6fff8c9761cfe4d4b8bca5cddf22..0000000000000000000000000000000000000000 --- a/src/main/java/org/example/scoreboard/IMatch.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.example.scoreboard; - -public interface IMatch { - -} diff --git a/src/main/java/org/example/scoreboard/IScore.java b/src/main/java/org/example/scoreboard/IScore.java deleted file mode 100644 index 992791a603edd2bbaf3fa96734cbdc1296824d2b..0000000000000000000000000000000000000000 --- a/src/main/java/org/example/scoreboard/IScore.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.example.scoreboard; - -public interface IScore { - -} diff --git a/src/main/java/org/example/scoreboard/IScoreboard.java b/src/main/java/org/example/scoreboard/IScoreboard.java deleted file mode 100644 index 6daaee6a95164324788c8d9d9c840264a4d50709..0000000000000000000000000000000000000000 --- a/src/main/java/org/example/scoreboard/IScoreboard.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.example.scoreboard; - -import java.util.ArrayList; - -public interface IScoreboard { - - IMatch startMatch(ITeam home, ITeam away); - - void finishMatch(IMatch match); - - IMatch updateScore(IMatch match, IScore home, IScore away); - - ArrayList getGameSummary(); -} diff --git a/src/main/java/org/example/scoreboard/ITeam.java b/src/main/java/org/example/scoreboard/ITeam.java deleted file mode 100644 index 1980e584a3ef093a1e29ab550686284bd6f3e07c..0000000000000000000000000000000000000000 --- a/src/main/java/org/example/scoreboard/ITeam.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.example.scoreboard; - -public interface ITeam { -} diff --git a/src/main/java/org/example/scoreboard/reference/api/IMatch.java b/src/main/java/org/example/scoreboard/reference/api/IMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..a0912d011b42579bc70bb9e69852f0e135b15774 --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/api/IMatch.java @@ -0,0 +1,17 @@ +package org.example.scoreboard.reference.api; + +import java.util.Date; +import java.util.UUID; + +public interface IMatch { + UUID getId(); + + ITeam getHomeTeam(); + IScore getHomeTeamScore(); + void setHomeTeamScore(IScore homeTeamScore); + + ITeam getAwayTeam(); + IScore getAwayTeamScore(); + void setAwayTeamScore(IScore awayTeamScore); + Date getStartDate(); +} diff --git a/src/main/java/org/example/scoreboard/reference/api/IScore.java b/src/main/java/org/example/scoreboard/reference/api/IScore.java new file mode 100644 index 0000000000000000000000000000000000000000..90cf15af4e9eb5dbc3cf6f34add5e8f8e9b81980 --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/api/IScore.java @@ -0,0 +1,5 @@ +package org.example.scoreboard.reference.api; + +public interface IScore { + Integer getGoals(); +} diff --git a/src/main/java/org/example/scoreboard/reference/api/IScoreboard.java b/src/main/java/org/example/scoreboard/reference/api/IScoreboard.java new file mode 100644 index 0000000000000000000000000000000000000000..a2a67fc61bef561663d1f9691b619b106950cd7e --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/api/IScoreboard.java @@ -0,0 +1,47 @@ +package org.example.scoreboard.reference.api; + +import java.security.InvalidParameterException; +import java.util.ArrayList; + +public interface IScoreboard { + + /** + * Starts a match between two teams. + * When a game starts, two teams are required. + * + * @param homeTeam the home team + * @param awayTeam the away team + * @throws InvalidParameterException the team is already in a match + * @return the match started + */ + IMatch startMatch(ITeam homeTeam, ITeam awayTeam); + + /** + * Finish game. + * It will remove a match from the scoreboard. + * + * @param match the match to finish + * @return the finished match + */ + IMatch finishMatch(IMatch match); + + /** + * Update team scores. + * Receiving the pair score; home team score and away team score updates a game score. + * + * @param match + * @param homeTeamScore + * @param awayTeamScore + * @return updated match + */ + IMatch updateScore(IMatch match, IScore homeTeamScore, IScore awayTeamScore); + + /** + * Get a summary of games by total score. + * Those games with the same total score will be returned ordered by the most recently added to our system. + * + * @return + */ + ArrayList getGameSummary(); + +} diff --git a/src/main/java/org/example/scoreboard/reference/api/ITeam.java b/src/main/java/org/example/scoreboard/reference/api/ITeam.java new file mode 100644 index 0000000000000000000000000000000000000000..f94ed48905dc12afd34e7e6ddbff991275055be8 --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/api/ITeam.java @@ -0,0 +1,8 @@ +package org.example.scoreboard.reference.api; + +import java.util.UUID; + +public interface ITeam { + UUID getId(); + String getName(); +} diff --git a/src/main/java/org/example/scoreboard/reference/generic/AbstractMatch.java b/src/main/java/org/example/scoreboard/reference/generic/AbstractMatch.java new file mode 100644 index 0000000000000000000000000000000000000000..5d7d60a26bddfd52efffb8d3c4967ec675f861ab --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/generic/AbstractMatch.java @@ -0,0 +1,105 @@ +package org.example.scoreboard.reference.generic; + +import org.example.scoreboard.reference.api.IMatch; +import org.example.scoreboard.reference.api.IScore; +import org.example.scoreboard.reference.api.ITeam; + +import java.util.Date; +import java.util.UUID; +import java.util.function.Supplier; + +public abstract class AbstractMatch implements IMatch, Comparable { + private final UUID id; + private final ITeam homeTeam; + private final ITeam awayTeam; + private IScore homeTeamScore; + private IScore awayTeamScore; + private final Date startDate; + + protected AbstractMatch(Supplier scoreSupplier, ITeam homeTeam, ITeam awayTeam) { + this.id = UUID.randomUUID(); + this.homeTeam = homeTeam; + this.awayTeam = awayTeam; + this.homeTeamScore = scoreSupplier.get(); + this.awayTeamScore = scoreSupplier.get(); + this.startDate = new Date(); + } + + public ITeam getHomeTeam() { + return homeTeam; + } + + public ITeam getAwayTeam() { + return awayTeam; + } + + public UUID getId() { + return id; + } + + public IScore getHomeTeamScore() { + return homeTeamScore; + } + + public void setHomeTeamScore(IScore homeTeamScore) { + this.homeTeamScore = homeTeamScore; + } + + public IScore getAwayTeamScore() { + return awayTeamScore; + } + + public void setAwayTeamScore(IScore awayTeamScore) { + this.awayTeamScore = awayTeamScore; + } + + public Date getStartDate() { + return startDate; + } + + @Override + public String toString() { + return getHomeTeam() + " " + getHomeTeamScore() + " - " + getAwayTeam() + " " + getAwayTeamScore(); + } + + @Override + public int compareTo(IMatch otherMatch) { + int totalScoreMatch1 = getHomeTeamScore().getGoals() + getAwayTeamScore().getGoals(); + int totalScoreMatch2 = otherMatch.getHomeTeamScore().getGoals() + otherMatch.getAwayTeamScore().getGoals(); + if (totalScoreMatch1 > totalScoreMatch2) { + return -1; + } else if (totalScoreMatch1 < totalScoreMatch2) { + return 1; + } else { + Date startMatch1 = getStartDate(); + Date startMatch2 = otherMatch.getStartDate(); + if (startMatch1.after(startMatch2)) { + return -1; + } else if(startMatch1.before(startMatch2)) { + return 1; + } + } + return 0; + } + + + /*@Override + int compare(IMatch match1, IMatch match2) { + int totalScoreMatch1 = match1.getHomeTeamScore().getGoals() + match1.getAwayTeamScore().getGoals(); + int totalScoreMatch2 = match2.getHomeTeamScore().getGoals() + match2.getAwayTeamScore().getGoals(); + if (totalScoreMatch1 > totalScoreMatch2) { + return 1; + } else if (totalScoreMatch1 < totalScoreMatch2) { + return -1; + } else { + Date startMatch1 = match1.getStartDate(); + Date startMatch2 = match2.getStartDate(); + if (startMatch1.after(startMatch2)) { + return 1; + } else if(startMatch1.before(startMatch2)) { + return -1; + } + } + return 0; + }*/ +} diff --git a/src/main/java/org/example/scoreboard/reference/generic/AbstractScore.java b/src/main/java/org/example/scoreboard/reference/generic/AbstractScore.java new file mode 100644 index 0000000000000000000000000000000000000000..dd9ceb9e7f8beb7a7dde01cfee8368cb64489e2e --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/generic/AbstractScore.java @@ -0,0 +1,24 @@ +package org.example.scoreboard.reference.generic; + +import org.example.scoreboard.reference.api.IScore; + +public abstract class AbstractScore implements IScore { + private final Integer goals; + + protected AbstractScore() { + this.goals = 0; + } + + protected AbstractScore(Integer goals) { + this.goals = goals; + } + + public Integer getGoals() { + return goals; + } + + @Override + public String toString() { + return goals.toString(); + } +} diff --git a/src/main/java/org/example/scoreboard/reference/generic/AbstractScoreboard.java b/src/main/java/org/example/scoreboard/reference/generic/AbstractScoreboard.java new file mode 100644 index 0000000000000000000000000000000000000000..4643f106fbee99f7a914fb0e5f09f88058cac6e0 --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/generic/AbstractScoreboard.java @@ -0,0 +1,70 @@ +package org.example.scoreboard.reference.generic; + +import org.example.scoreboard.reference.api.IMatch; +import org.example.scoreboard.reference.api.IScore; +import org.example.scoreboard.reference.api.IScoreboard; +import org.example.scoreboard.reference.api.ITeam; + +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.NoSuchElementException; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class AbstractScoreboard implements IScoreboard { + + BiFunction matchSupplier; + + HashMap teamsOnTheBoard = new HashMap<>(); + HashMap ongoingMatches = new HashMap<>(); + + private AbstractScoreboard(){} + public AbstractScoreboard(BiFunction matchSupplier) { + this.matchSupplier = matchSupplier; + } + + @Override + public IMatch startMatch(ITeam homeTeam, ITeam awayTeam) { + if(teamsOnTheBoard.putIfAbsent(homeTeam.getId(), homeTeam) != null) { + throw new InvalidParameterException("Home team already on the board"); + } + + if (teamsOnTheBoard.putIfAbsent(awayTeam.getId(), awayTeam) != null) { + teamsOnTheBoard.remove(homeTeam.getId()); + throw new InvalidParameterException("Away team already on the board"); + } + + IMatch match = matchSupplier.apply(homeTeam, awayTeam); + ongoingMatches.put(match.getId(), match); + return match; + } + + @Override + public IMatch finishMatch(IMatch match) { + if (!ongoingMatches.containsKey(match.getId())) { + throw new NoSuchElementException(); + } + IMatch finishedIMatch = ongoingMatches.remove(match.getId()); + Stream.of(finishedIMatch.getHomeTeam(), finishedIMatch.getAwayTeam()).map(ITeam::getId).forEach(teamsOnTheBoard::remove); + return finishedIMatch; + } + + @Override + public IMatch updateScore(IMatch match, IScore homeTeamScore, IScore awayTeamScore) { + if (!ongoingMatches.containsKey(match.getId())) { + throw new NoSuchElementException(); + } + IMatch storedIMatch = ongoingMatches.get(match.getId()); + storedIMatch.setHomeTeamScore(homeTeamScore); + storedIMatch.setAwayTeamScore(awayTeamScore); + return storedIMatch; + } + + @Override + public ArrayList getGameSummary() { + return ongoingMatches.values().stream().sorted().collect(Collectors.toCollection(ArrayList::new)); + } +} diff --git a/src/main/java/org/example/scoreboard/reference/generic/AbstractTeam.java b/src/main/java/org/example/scoreboard/reference/generic/AbstractTeam.java new file mode 100644 index 0000000000000000000000000000000000000000..0d904d1e0d8d6edfde951e100f504c09aa2f0eb3 --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/generic/AbstractTeam.java @@ -0,0 +1,28 @@ +package org.example.scoreboard.reference.generic; + +import org.example.scoreboard.reference.api.ITeam; + +import java.util.UUID; + +public abstract class AbstractTeam implements ITeam { + private final UUID id; + private final String name; + + protected AbstractTeam(String name) { + this.id = UUID.randomUUID(); + this.name = name; + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/src/main/java/org/example/scoreboard/reference/reference/Match.java b/src/main/java/org/example/scoreboard/reference/reference/Match.java new file mode 100644 index 0000000000000000000000000000000000000000..92c6533d40723448197a1237e83211294e3ee17a --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/reference/Match.java @@ -0,0 +1,10 @@ +package org.example.scoreboard.reference.reference; + +import org.example.scoreboard.reference.api.ITeam; +import org.example.scoreboard.reference.generic.AbstractMatch; + +public class Match extends AbstractMatch { + public Match(ITeam homeTeam, ITeam awayTeam) { + super(Score::new, homeTeam, awayTeam); + } +} diff --git a/src/main/java/org/example/scoreboard/reference/reference/Score.java b/src/main/java/org/example/scoreboard/reference/reference/Score.java new file mode 100644 index 0000000000000000000000000000000000000000..8ab2337a341b81b0ac2a0ea79ed3f555b0a931a3 --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/reference/Score.java @@ -0,0 +1,12 @@ +package org.example.scoreboard.reference.reference; + +import org.example.scoreboard.reference.generic.AbstractScore; + +public class Score extends AbstractScore { + public Score() { + super(); + } + public Score(Integer goals) { + super(goals); + } +} diff --git a/src/main/java/org/example/scoreboard/reference/reference/Scoreboard.java b/src/main/java/org/example/scoreboard/reference/reference/Scoreboard.java new file mode 100644 index 0000000000000000000000000000000000000000..64ba5498122e8c92866536235b862d03d84e061a --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/reference/Scoreboard.java @@ -0,0 +1,9 @@ +package org.example.scoreboard.reference.reference; + +import org.example.scoreboard.reference.generic.AbstractScoreboard; + +public class Scoreboard extends AbstractScoreboard { + public Scoreboard() { + super(Match::new); + } +} diff --git a/src/main/java/org/example/scoreboard/reference/reference/Team.java b/src/main/java/org/example/scoreboard/reference/reference/Team.java new file mode 100644 index 0000000000000000000000000000000000000000..2fa87e167ddcc452f1634c0928059b18c348305b --- /dev/null +++ b/src/main/java/org/example/scoreboard/reference/reference/Team.java @@ -0,0 +1,9 @@ +package org.example.scoreboard.reference.reference; + +import org.example.scoreboard.reference.generic.AbstractTeam; + +public class Team extends AbstractTeam { + public Team(String name) { + super(name); + } +} diff --git a/src/test/java/org/example/AppTest.java b/src/test/java/org/example/AppTest.java deleted file mode 100644 index 6a1d2d79f7878de5144c8a33c09e56d788dfdacb..0000000000000000000000000000000000000000 --- a/src/test/java/org/example/AppTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.example; - -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -/** - * Unit test for simple App. - */ -public class AppTest -{ - /** - * Rigorous Test :-) - */ - @Test - public void shouldAnswerWithTrue() - { - assertTrue( true ); - } -} diff --git a/src/test/java/org/example/scoreboard/reference/TestScoreboard.java b/src/test/java/org/example/scoreboard/reference/TestScoreboard.java new file mode 100644 index 0000000000000000000000000000000000000000..99493264a0899278ffa8e39f6483f5c1da5a9347 --- /dev/null +++ b/src/test/java/org/example/scoreboard/reference/TestScoreboard.java @@ -0,0 +1,157 @@ +package org.example.scoreboard.reference; + +import org.example.scoreboard.reference.api.IMatch; +import org.example.scoreboard.reference.api.IScore; +import org.example.scoreboard.reference.reference.Match; +import org.example.scoreboard.reference.reference.Score; +import org.example.scoreboard.reference.reference.Scoreboard; +import org.example.scoreboard.reference.reference.Team; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.Thread.sleep; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class TestScoreboard { + + private Scoreboard scoreboard; + + private Team Mexico; + private Team Canada; + private Team Spain; + private Team Brazil; + private Team Germany; + private Team France; + private Team Uruguay; + private Team Italy; + private Team Argentina; + private Team Australia; + + @Before + public void setup() { + scoreboard = spy(new Scoreboard()); + + Mexico = new Team("Mexico"); + Canada = new Team("Canada"); + Spain = new Team("Spain"); + Brazil = new Team("Brazil"); + Germany = new Team("Germany"); + France = new Team("France"); + Uruguay = new Team("Uruguay"); + Italy = new Team("Italy"); + Argentina = new Team("Argentina"); + Australia = new Team("Australia"); + } + + @After + public void teardown() { + scoreboard = null; + + Mexico = null; + Canada = null; + Spain = null; + Brazil = null; + Germany = null; + France = null; + Uruguay = null; + Italy = null; + Argentina = null; + Australia = null; + } + + // should start a game when teams are not in a game + @Test + public void shouldStartAGame_whenTeamsAreNotInAGame() { + assertNotNull(scoreboard.startMatch(Mexico, Canada)); + } + + // should finish a game when the game has been started before + @Test + public void shouldFinishAGame_whenGameHasBeenStarted() { + IMatch match = scoreboard.startMatch(Mexico, Canada); + if (match != scoreboard.finishMatch(match)) { + fail("unable to finish game"); + } + } + + // should throw NoSuchElementException match is not ongoing + @Test + public void shouldThrowNoSuchElementException_whenMatchNotFound() { + Match match = new Match(Mexico, Canada); + try { + scoreboard.finishMatch(match); + fail("exception was not thrown"); + } catch (NoSuchElementException ignored) {} + } + + // summary should be empty when no match has started + @Test + public void shouldBeEmptySummary_whenNoMatchHasStarted() { + if(!scoreboard.getGameSummary().isEmpty()) { + fail("Summary was not empty on creation."); + } + } + + // should get game in summary when the game has been started before + @Test + public void shouldGetGameInSummary_whenGameHasBeenStarted() { + IMatch match = scoreboard.startMatch(Mexico, Canada); + if (match != scoreboard.getGameSummary().get(0)) { + fail("unable to get single ongoing game"); + } + } + + // score for a match that just started should be 0-0 + @Test + public void scoreShouldBeZeroZero_whenMatchJustStarted() { + IMatch match = scoreboard.startMatch(Mexico, Canada); + if (match.getHomeTeamScore().getGoals() != 0 || match.getAwayTeamScore().getGoals() != 0) { + fail("initial score was not 0-0"); + } + } + + // score increase for home team when home team score is updated to + 1 + @Test + public void shouldIncreaseScoreForHomeTeam_whenHomeTeamScoreIsUpdated() { + IMatch match = scoreboard.startMatch(Mexico, Canada); + IScore updatedHomeTeamScore = new Score(match.getHomeTeamScore().getGoals() + 1); + scoreboard.updateScore(match, updatedHomeTeamScore, match.getAwayTeamScore()); + if (match.getHomeTeamScore().getGoals() != 1 || match.getAwayTeamScore().getGoals() != 0) { + fail("updated score was not 1-0"); + } + } + + // should return ordered matches_whenSummaryIsRequested + @Test + public void shouldReturnOrderedMatches_whenSummaryIsRequested() throws InterruptedException { + IMatch match1 = scoreboard.startMatch(Mexico, Canada); + match1 = scoreboard.updateScore(match1, new Score(0), new Score(5)); + sleep(1); + IMatch match2 = scoreboard.startMatch(Spain, Brazil); + match2 = scoreboard.updateScore(match2, new Score(10), new Score(2)); + sleep(1); + IMatch match3 = scoreboard.startMatch(Germany, France); + match3 = scoreboard.updateScore(match3, new Score(2), new Score(2)); + sleep(1); + IMatch match4 = scoreboard.startMatch(Uruguay, Italy); + match4 = scoreboard.updateScore(match4, new Score(6), new Score(6)); + sleep(1); + IMatch match5 = scoreboard.startMatch(Argentina, Australia); + match5 = scoreboard.updateScore(match5, new Score(3), new Score(1)); + sleep(1); + + ArrayList expectedResult = Stream.of(match4, match2, match1, match5, match3).collect(Collectors.toCollection(ArrayList::new)); + ArrayList gameSummary = scoreboard.getGameSummary(); + + if (!expectedResult.equals(gameSummary)) { + gameSummary.forEach(System.out::println); + fail("failed to return expected ordered summary"); + } + } +}