Commit 6cd1dcbb authored by João Lino's avatar João Lino

simple reference implementation

parent ee56d1e5
Pipeline #7 canceled with stages
# 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
```
......@@ -22,9 +22,17 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
......@@ -71,5 +79,15 @@
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
package org.example;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}
package org.example.scoreboard;
public interface IMatch {
}
package org.example.scoreboard;
public interface IScore {
}
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<IMatch> getGameSummary();
}
package org.example.scoreboard;
public interface ITeam {
}
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();
}
package org.example.scoreboard.reference.api;
public interface IScore {
Integer getGoals();
}
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<IMatch> getGameSummary();
}
package org.example.scoreboard.reference.api;
import java.util.UUID;
public interface ITeam {
UUID getId();
String getName();
}
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<IMatch> {
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<IScore> 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;
}*/
}
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();
}
}
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<ITeam, ITeam, IMatch> matchSupplier;
HashMap<UUID, ITeam> teamsOnTheBoard = new HashMap<>();
HashMap<UUID, IMatch> ongoingMatches = new HashMap<>();
private AbstractScoreboard(){}
public AbstractScoreboard(BiFunction<ITeam, ITeam, IMatch> 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<IMatch> getGameSummary() {
return ongoingMatches.values().stream().sorted().collect(Collectors.toCollection(ArrayList::new));
}
}
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();
}
}
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);
}
}
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);
}
}
package org.example.scoreboard.reference.reference;
import org.example.scoreboard.reference.generic.AbstractScoreboard;
public class Scoreboard extends AbstractScoreboard {
public Scoreboard() {
super(Match::new);
}
}
package org.example.scoreboard.reference.reference;
import org.example.scoreboard.reference.generic.AbstractTeam;
public class Team extends AbstractTeam {
public Team(String name) {
super(name);
}
}
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 );
}
}
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<IMatch> expectedResult = Stream.of(match4, match2, match1, match5, match3).collect(Collectors.toCollection(ArrayList::new));
ArrayList<IMatch> gameSummary = scoreboard.getGameSummary();
if (!expectedResult.equals(gameSummary)) {
gameSummary.forEach(System.out::println);
fail("failed to return expected ordered summary");
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment