com.danielflower.crickam.scorer.Match Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cricket-scorer Show documentation
Show all versions of cricket-scorer Show documentation
An immutable java library for keeping score of cricket matches
package com.danielflower.crickam.scorer;
import com.danielflower.crickam.scorer.events.*;
import java.time.Instant;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.TimeZone;
import java.util.UUID;
import static com.danielflower.crickam.scorer.Crictils.toInteger;
import static com.danielflower.crickam.scorer.ImmutableList.emptyList;
import static java.util.Objects.requireNonNull;
/**
* A lovely game of cricket between two teams.
* To create a new match, pass a builder from {@link MatchEvents#matchStarting(MatchType)} to {@link MatchControl#newMatch(MatchStartingEvent.Builder)}
*/
public final class Match {
public enum State {
NOT_STARTED, ABANDONED, IN_PROGRESS, COMPLETED;
}
private final FixedData data;
private final State state;
private final MatchResult result;
private final ImmutableList completedInningsList;
private final Innings currentInnings;
private final Balls balls;
private final String versionId = UUID.randomUUID().toString();
private Match(FixedData data, State state, MatchResult result, ImmutableList completedInningsList, Innings currentInnings, Balls balls) {
this.data = requireNonNull(data);
this.state = requireNonNull(state);
this.result = result;
this.completedInningsList = requireNonNull(completedInningsList);
this.currentInnings = currentInnings;
this.balls = requireNonNull(balls);
}
static Match newMatch(MatchStartingEvent e) {
Venue venue = e.venue().orElse(null);
TimeZone timeZone = e.timeZone().orElse(null);
FixedData fd = new FixedData(e.matchID(), e.series().orElse(null), e.time().orElse(null), e.scheduledStartTime().orElse(null),
e.teamLineUps(), e.matchType(), e.inningsPerTeam(), toInteger(e.oversPerInnings()), venue,
e.numberOfScheduledDays(), toInteger(e.ballsPerInnings()), timeZone);
return new Match(fd, State.NOT_STARTED, null, emptyList(), null, new Balls());
}
/**
* @return An ID that uniquely represents the match in its current state (i.e. unlike {@link #matchID()}, this
* value changes every time a new event is applied.
*/
public String versionID() {
return versionId;
}
/**
* @return A list of every ball bowled in the match
*/
public Balls balls() {
return balls;
}
/**
* @return The current match state
*/
public State state() {
return state;
}
/**
* @return The current innings in the match, if an innings is in progress
* @see #inningsList()
* @see #currentInnings()
*/
public Optional currentInnings() {
return Optional.ofNullable(currentInnings);
}
/**
* @return The innings played in the match that have been completed
*/
public ImmutableList completedInningsList() {
return completedInningsList;
}
/**
* @return All innings in the match, including any in-progress innings
*/
public ImmutableList inningsList() {
return currentInnings == null ? completedInningsList : completedInningsList.add(currentInnings);
}
/**
* @return A unique ID for this match
*/
public String matchID() {
return data.matchID;
}
public Optional series() {
return Optional.ofNullable(data.series);
}
/**
* @return The time when the match event was created
*/
public Optional time() {
return Optional.ofNullable(data.time);
}
/**
* @return The timezone that the match is played in
*/
public Optional timeZone() {
return Optional.ofNullable(data.timeZone);
}
/**
* @return The scheduled start time of the match, if known.
*/
public Optional scheduledStartTime() {
return Optional.ofNullable(data.scheduledStartTime);
}
/**
* @return The teams playing in this match, in no particular order.
*/
public ImmutableList teams() {
return data.teams;
}
/**
* @return The number of scheduled balls per innings, or empty if there is no limit.
*/
public OptionalInt ballsPerInnings() {
return Crictils.toOptional(data.ballsPerInnings);
}
/**
* @return The number of scheduled balls per innings, or -1 if there is no limit.
*/
public OptionalInt oversPerInnings() {
return Crictils.toOptional(data.oversPerInnings);
}
public MatchType matchType() {
return data.matchType;
}
/**
* @return The number of innings per team. Generally 1 for limited overs matches and 2 for first class / test matches.
*/
public int numberOfInningsPerTeam() {
return data.inningsPerTeam;
}
public Optional venue() {
return Optional.ofNullable(data.venue);
}
/**
* @return The number of days this match goes for. Generally 1 for limited overs matches, and 5 for test matches.
*/
public int numberOfScheduledDays() {
return data.numberOfScheduledDays;
}
/**
* @return The final result of the match, or empty if {@link #state()} is not {@link State#COMPLETED}
*/
public Optional result() {
return Optional.ofNullable(result);
}
/**
* Guesses the result of a match.
* Note that on the conclusion of the match, an actual result is determined and accessed by {@link #result()}
* (and this may return a different value than this method).
*
* @return Calculates what the current result may be at the current point in time.
*/
public MatchResult calculateResult() {
return MatchResult.fromMatch(this);
}
Match onEvent(MatchEvent event) {
if (event instanceof BallCompletedEvent && currentInnings().isEmpty()) {
throw new IllegalStateException("Cannot process a ball when there is no active innings");
}
State newState = this.state;
MatchResult newResult = this.result;
Balls newBalls = this.balls;
Innings newCurrentInnings = this.currentInnings;
ImmutableList newInningsList = completedInningsList;
if (event instanceof InningsStartingEvent) {
newState = State.IN_PROGRESS;
newCurrentInnings = Innings.newInnings((InningsStartingEvent) event);
} else if (event instanceof MatchCompletedEvent) {
// don't pass to the innings
newState = State.COMPLETED;
newResult = ((MatchCompletedEvent) event).result().orElseGet(this::calculateResult);
} else {
if (newCurrentInnings != null) {
if (event instanceof BallCompletedEvent) {
newBalls = newBalls.add((BallCompletedEvent) event);
}
newCurrentInnings = newCurrentInnings.onEvent(event);
if (event instanceof InningsCompletedEvent) {
newInningsList = newInningsList.add(newCurrentInnings);
newCurrentInnings = null;
}
}
}
return new Match(data, newState, newResult, newInningsList, newCurrentInnings, newBalls);
}
/**
* Gets the score for the given team line-up for the entire match
*
* @param team The team to find the score for
* @return The score for all innings the team has played in this match
*/
public Score scoredByTeam(LineUp team) {
Score total = Score.EMPTY;
for (Innings innings : completedInningsList) {
if (innings.battingTeam().equals(team)) {
total = total.add(innings.score());
}
}
return total;
}
/**
* @param someTeam Either the bowling team line-up or the batting team line-up
* @return The bowling team if the batting team is given; or the batting team if the bowling team is given
* @throws java.util.NoSuchElementException An invalid line-up was given
*/
public LineUp otherTeam(LineUp someTeam) {
return this.teams().stream().filter(t -> !t.equals(someTeam)).findFirst().orElseThrow();
}
private static class FixedData {
private final String matchID;
private final Series series;
private final Instant time;
private final Instant scheduledStartTime;
private final ImmutableList teams;
private final MatchType matchType;
private final int inningsPerTeam;
private final Integer oversPerInnings;
private final Venue venue;
private final int numberOfScheduledDays;
private final Integer ballsPerInnings;
private final TimeZone timeZone;
public FixedData(String matchID, Series series, Instant time, Instant scheduledStartTime, ImmutableList teams, MatchType matchType, int inningsPerTeam, Integer oversPerInnings, Venue venue, int numberOfScheduledDays, Integer ballsPerInnings, TimeZone timeZone) {
this.matchID = matchID;
this.series = series;
this.time = time;
this.scheduledStartTime = scheduledStartTime;
this.teams = teams;
this.matchType = matchType;
this.inningsPerTeam = inningsPerTeam;
this.oversPerInnings = oversPerInnings;
this.venue = venue;
this.numberOfScheduledDays = numberOfScheduledDays;
this.ballsPerInnings = ballsPerInnings;
this.timeZone = timeZone;
}
}
}