Volleyball scoring
Custom patterns decide what the latest point means, so scoring is a plain match.
module volleyball
import nanyx/time
-- Scoring a volleyball game. Custom patterns decide what the latest point
-- means for the game, so the scoring function reads as a plain match.
export type TeamColor = #yellow | #black
export def TeamColor.parse: string -> #some(TeamColor) | #error(#invalidColor) = {
| "yellow" -> #some(#yellow)
| "black" -> #some(#black)
| _ -> #error(#invalidColor)
}
export def TeamColor.show: TeamColor -> string = {
| #yellow -> "yellow"
| #black -> "black"
}
type GameId = int
type TeamId = string
type Rules = (maxSetPoints: int, maxSets: int)
type Score = (by: (team: TeamId, color: TeamColor), at: Instant)
type SetScores = list(Score) -- the points of one set
type GameScores = list(SetScores) -- finished sets first, the set in play last
type OpenGame = (
id: GameId
teams: (TeamId, TeamId)
startedAt: Instant
rules: Rules
score: GameScores
)
type FinishedGame = (
id: GameId
teams: (TeamId, TeamId)
startedAt: Instant
finishedAt: Instant
rules: Rules
score: GameScores
)
export type Game = #open(OpenGame) | #finished(FinishedGame)
def setPoints: (SetScores, TeamId) -> int = { set, team -> set \count { s -> s.by.team == team } }
def setWinner: (Rules, (TeamId, TeamId), SetScores) -> #some(TeamId) | #none = { rules, teams, set ->
def (home, away) = teams
def homePoints = setPoints(set, home)
def awayPoints = setPoints(set, away)
if | homePoints >= rules.maxSetPoints and homePoints - awayPoints >= 2 -> #some(home)
| awayPoints >= rules.maxSetPoints and awayPoints - homePoints >= 2 -> #some(away)
| else -> #none
}
def setsWon: (OpenGame, TeamId) -> int = { game, team ->
game.score \count { set -> setWinner(game.rules, game.teams, set) == #some(team) }
}
-- What the latest point did to the game
pattern OpenGame -> #SetInPlay | #SetWon | #GameWon = { game ->
def #some(currentSet) = game.score \last except #emptyList -> #some([])
match setWinner(game.rules, game.teams, currentSet)
| #none -> #SetInPlay
| #some(winner) ->
def setsToWin = game.rules.maxSets / 2 + 1
if setsWon(game, winner) >= setsToWin then #GameWon else #SetWon
}
export def recordScore: (OpenGame, by: (team: TeamId, color: TeamColor), at: Instant) -> Game = { game, by, at ->
def point = Score(by = by, at = at)
def finishedSets = game.score \dropLast
def #some(currentSet) = game.score \last except #emptyList -> #some([])
def updated = game with score = [...finishedSets, [...currentSet, point]]
match updated
| #SetInPlay -> #open(updated)
| #SetWon -> #open(updated with score = [...updated.score, []])
| #GameWon -> #finished(FinishedGame(
id = updated.id
teams = updated.teams
startedAt = updated.startedAt
finishedAt = at
rules = updated.rules
score = updated.score
))
}
export def openGame: (Rules, (TeamId, TeamId), Instant, GameId) -> #some(OpenGame) | #error(#teamsMustBeUnique)
= { rules, teams, startedAt, id ->
def (t1, t2) = teams
if t1 == t2 then #error(#teamsMustBeUnique)
else #some(OpenGame(id = id, teams = teams, startedAt = startedAt, rules = rules, score = [[]]))
}
This program lives in the repository as examples/teamColours.nyx.