Skip to content

Instantly share code, notes, and snippets.

@branneman
Last active March 23, 2024 14:53
Show Gist options
  • Save branneman/e3eb6ba8101e1003279cadcff8b656c5 to your computer and use it in GitHub Desktop.
Save branneman/e3eb6ba8101e1003279cadcff8b656c5 to your computer and use it in GitHub Desktop.
Six Nations table calculator
import { describe, expect, it } from 'vitest'
import { Table, Match } from './six-nations-table'
import {
matches2table,
updateTable,
getTable,
orderTable,
PgamesPlayed,
WgamesWon,
LgamesLost,
DgamesDrawn,
PFmatchPointsFor,
PAmatchPointsAgainst,
DIFFpointsDifference,
TFtriesFor,
TAtriesAgainst,
BPtryBonusPoints,
BPlosingBonusPoints,
PTScompetitionPoints,
PTSgrandSlam,
} from './six-nations-table'
describe('matches2table()', () => {
describe('returns the correct score table', () => {
it('m6n 2024', () => {
// prettier-ignore
const matches: Array<Match> = [
[ { team: 'FRA', PF: 17, TF: 2 }, { team: 'IRE', PF: 38, TF: 5 } ],
[ { team: 'ITA', PF: 24, TF: 3 }, { team: 'ENG', PF: 27, TF: 2 } ],
[ { team: 'WAL', PF: 26, TF: 4 }, { team: 'SCO', PF: 27, TF: 3 } ],
[ { team: 'SCO', PF: 16, TF: 1 }, { team: 'FRA', PF: 20, TF: 2 } ],
[ { team: 'ENG', PF: 16, TF: 2 }, { team: 'WAL', PF: 14, TF: 2 } ], // WAL got penalty try
[ { team: 'IRE', PF: 36, TF: 6 }, { team: 'ITA', PF: 0, TF: 0 } ],
[ { team: 'IRE', PF: 31, TF: 4 }, { team: 'WAL', PF: 7, TF: 1 } ], // WAL got penalty try
[ { team: 'SCO', PF: 30, TF: 3 }, { team: 'ENG', PF: 21, TF: 2 } ],
[ { team: 'FRA', PF: 13, TF: 1 }, { team: 'ITA', PF: 13, TF: 1 } ],
[ { team: 'ITA', PF: 31, TF: 3 }, { team: 'SCO', PF: 29, TF: 4 } ],
[ { team: 'ENG', PF: 23, TF: 3 }, { team: 'IRE', PF: 22, TF: 2 } ],
[ { team: 'WAL', PF: 24, TF: 3 }, { team: 'FRA', PF: 45, TF: 5 } ],
[ { team: 'WAL', PF: 21, TF: 3 }, { team: 'ITA', PF: 24, TF: 2 } ],
[ { team: 'IRE', PF: 17, TF: 2 }, { team: 'SCO', PF: 13, TF: 1 } ],
[ { team: 'FRA', PF: 33, TF: 3 }, { team: 'ENG', PF: 31, TF: 4 } ],
]
// prettier-ignore
const table = [
{ team: 'IRE', P: 5, W: 4, D: 0, L: 1, PF: 144, PA: 60, DIFF: 84, TF: 19, TA: 7, BP: 4, PTS: 20 },
{ team: 'FRA', P: 5, W: 3, D: 1, L: 1, PF: 128, PA: 122, DIFF: 6, TF: 13, TA: 14, BP: 1, PTS: 15 },
{ team: 'ENG', P: 5, W: 3, D: 0, L: 2, PF: 118, PA: 123, DIFF: -5, TF: 13, TA: 13, BP: 2, PTS: 14 },
{ team: 'SCO', P: 5, W: 2, D: 0, L: 3, PF: 115, PA: 115, DIFF: 0, TF: 12, TA: 13, BP: 4, PTS: 12 },
{ team: 'ITA', P: 5, W: 2, D: 1, L: 2, PF: 92, PA: 126, DIFF: -34, TF: 9, TA: 16, BP: 1, PTS: 11 },
{ team: 'WAL', P: 5, W: 0, D: 0, L: 5, PF: 92, PA: 143, DIFF: -51, TF: 13, TA: 16, BP: 4, PTS: 4 }
]
expect(matches2table(matches)).toEqual(table)
})
})
})
describe('updateTable()', () => {
it('populates an empty table', () => {
const table: Table = []
const match: Match = [
{ team: 'FRA', PF: 17, TF: 2 },
{ team: 'IRE', PF: 38, TF: 5 },
]
// prettier-ignore
const newTable = [
{ team: 'FRA', P: 1, W: 0, D: 0, L: 1, PF: 17, PA: 38, DIFF: -21, TF: 2, TA: 5, BP: 0, PTS: 0 },
{ team: 'IRE', P: 1, W: 1, D: 0, L: 0, PF: 38, PA: 17, DIFF: 21, TF: 5, TA: 2, BP: 1, PTS: 5 },
]
expect(updateTable(table, match)).toEqual(newTable)
})
it('adds to an existing table (1)', () => {
// prettier-ignore
const table = [
{ team: 'FRA', P: 1, W: 0, D: 0, L: 1, PF: 17, PA: 38, DIFF: -21, TF: 2, TA: 5, BP: 0, PTS: 0 },
{ team: 'IRE', P: 1, W: 1, D: 0, L: 0, PF: 38, PA: 17, DIFF: 21, TF: 5, TA: 2, BP: 0, PTS: 4 },
]
const match: Match = [
{ team: 'FRA', PF: 10, TF: 1 },
{ team: 'IRE', PF: 10, TF: 1 },
]
// prettier-ignore
const newTable = [
{ team: 'FRA', P: 2, W: 0, D: 1, L: 1, PF: 27, PA: 48, DIFF: -21, TF: 3, TA: 6, BP: 0, PTS: 2 },
{ team: 'IRE', P: 2, W: 1, D: 1, L: 0, PF: 48, PA: 27, DIFF: 21, TF: 6, TA: 3, BP: 0, PTS: 6 },
]
expect(updateTable(table, match)).toEqual(newTable)
})
it('adds to an existing table (2)', () => {
// prettier-ignore
const table = [
{ team: 'SCO', P: 1, W: 0, D: 0, L: 1, PF: 17, PA: 38, DIFF: -21, TF: 2, TA: 5, BP: 0, PTS: 0 },
{ team: 'WAL', P: 1, W: 1, D: 0, L: 0, PF: 38, PA: 17, DIFF: 21, TF: 5, TA: 2, BP: 0, PTS: 4 },
]
const match: Match = [
{ team: 'SCO', PF: 10, TF: 1 },
{ team: 'WAL', PF: 5, TF: 1 },
]
// prettier-ignore
const newTable = [
{ team: 'SCO', P: 2, W: 1, D: 0, L: 1, PF: 27, PA: 43, DIFF: -16, TF: 3, TA: 6, BP: 0, PTS: 4 },
{ team: 'WAL', P: 2, W: 1, D: 0, L: 1, PF: 43, PA: 27, DIFF: 16, TF: 6, TA: 3, BP: 1, PTS: 5 },
]
expect(updateTable(table, match)).toEqual(newTable)
})
})
describe('orderTable()', () => {
// prettier-ignore
const tpl = { P: 0, W: 0, D: 0, L: 0, PF: 0, PA: 0, DIFF: 0, TF: 0, TA: 0, BP: 0, PTS: 0 }
it('first sorts by PTS', () => {
const table: Table = [
{ team: 'ENG', ...tpl, PTS: 12, DIFF: 0, TF: 0 },
{ team: 'FRA', ...tpl, PTS: 10, DIFF: 0, TF: 0 },
{ team: 'IRE', ...tpl, PTS: 16, DIFF: 0, TF: 0 },
{ team: 'WAL', ...tpl, PTS: 3, DIFF: 0, TF: 0 },
{ team: 'SCO', ...tpl, PTS: 11, DIFF: 0, TF: 0 },
{ team: 'ITA', ...tpl, PTS: 7, DIFF: 0, TF: 0 },
]
expect(orderTable(table)).toEqual([
{ team: 'IRE', ...tpl, PTS: 16, DIFF: 0, TF: 0 },
{ team: 'ENG', ...tpl, PTS: 12, DIFF: 0, TF: 0 },
{ team: 'SCO', ...tpl, PTS: 11, DIFF: 0, TF: 0 },
{ team: 'FRA', ...tpl, PTS: 10, DIFF: 0, TF: 0 },
{ team: 'ITA', ...tpl, PTS: 7, DIFF: 0, TF: 0 },
{ team: 'WAL', ...tpl, PTS: 3, DIFF: 0, TF: 0 },
])
})
it('secondarily sorts by DIFF (with equal PTS)', () => {
const table: Table = [
{ team: 'ENG', ...tpl, PTS: 10, DIFF: 5, TF: 0 },
{ team: 'FRA', ...tpl, PTS: 10, DIFF: 15, TF: 0 },
{ team: 'IRE', ...tpl, PTS: 16, DIFF: 0, TF: 0 },
{ team: 'WAL', ...tpl, PTS: 3, DIFF: 0, TF: 0 },
{ team: 'SCO', ...tpl, PTS: 10, DIFF: 10, TF: 0 },
{ team: 'ITA', ...tpl, PTS: 7, DIFF: 0, TF: 0 },
]
expect(orderTable(table)).toEqual([
{ team: 'IRE', ...tpl, PTS: 16, DIFF: 0, TF: 0 },
{ team: 'FRA', ...tpl, PTS: 10, DIFF: 15, TF: 0 },
{ team: 'SCO', ...tpl, PTS: 10, DIFF: 10, TF: 0 },
{ team: 'ENG', ...tpl, PTS: 10, DIFF: 5, TF: 0 },
{ team: 'ITA', ...tpl, PTS: 7, DIFF: 0, TF: 0 },
{ team: 'WAL', ...tpl, PTS: 3, DIFF: 0, TF: 0 },
])
})
it('lastly sorts by TF (with equal PTS and DIFF)', () => {
const table: Table = [
{ team: 'ENG', ...tpl, PTS: 10, DIFF: 5, TF: 10 },
{ team: 'FRA', ...tpl, PTS: 10, DIFF: 5, TF: 20 },
{ team: 'IRE', ...tpl, PTS: 16, DIFF: 0, TF: 0 },
{ team: 'WAL', ...tpl, PTS: 3, DIFF: 0, TF: 0 },
{ team: 'SCO', ...tpl, PTS: 10, DIFF: 5, TF: 5 },
{ team: 'ITA', ...tpl, PTS: 7, DIFF: 0, TF: 0 },
]
expect(orderTable(table)).toEqual([
{ team: 'IRE', ...tpl, PTS: 16, DIFF: 0, TF: 0 },
{ team: 'FRA', ...tpl, PTS: 10, DIFF: 5, TF: 20 },
{ team: 'ENG', ...tpl, PTS: 10, DIFF: 5, TF: 10 },
{ team: 'SCO', ...tpl, PTS: 10, DIFF: 5, TF: 5 },
{ team: 'ITA', ...tpl, PTS: 7, DIFF: 0, TF: 0 },
{ team: 'WAL', ...tpl, PTS: 3, DIFF: 0, TF: 0 },
])
})
})
describe('getTable()', () => {
it('adds both teams when not in table yet', () => {
const table: Table = []
const match: Match = [
{ team: 'SCO', PF: 0, TF: 0 },
{ team: 'ENG', PF: 0, TF: 0 },
]
// prettier-ignore
const newTable: Table = [
{ team: 'SCO', P: 0, W: 0, D: 0, L: 0, PF: 0, PA: 0, DIFF: 0, TF: 0, TA: 0, BP: 0, PTS: 0 },
{ team: 'ENG', P: 0, W: 0, D: 0, L: 0, PF: 0, PA: 0, DIFF: 0, TF: 0, TA: 0, BP: 0, PTS: 0 }
]
const team1 = newTable[0]
const team2 = newTable[1]
expect(getTable(table, match)).toEqual([newTable, team1, team2])
})
it('adds first team when not in table yet, returns second as-is', () => {
// prettier-ignore
const table: Table = [
{ team: 'SCO', P: 1, W: 1, D: 0, L: 0, PF: 7, PA: 0, DIFF: 7, TF: 1, TA: 0, BP: 0, PTS: 4 }
]
const match: Match = [
{ team: 'SCO', PF: 0, TF: 0 },
{ team: 'ENG', PF: 0, TF: 0 },
]
// prettier-ignore
const newTable: Table = [
{ team: 'SCO', P: 1, W: 1, D: 0, L: 0, PF: 7, PA: 0, DIFF: 7, TF: 1, TA: 0, BP: 0, PTS: 4 },
{ team: 'ENG', P: 0, W: 0, D: 0, L: 0, PF: 0, PA: 0, DIFF: 0, TF: 0, TA: 0, BP: 0, PTS: 0 },
]
const team1 = newTable[0]
const team2 = newTable[1]
expect(getTable(table, match)).toEqual([newTable, team1, team2])
})
it('adds second team when not in table yet, returns first as-is', () => {
// prettier-ignore
const table: Table = [
{ team: 'ENG', P: 1, W: 1, D: 0, L: 0, PF: 7, PA: 0, DIFF: 7, TF: 1, TA: 0, BP: 0, PTS: 4 }
]
const match: Match = [
{ team: 'SCO', PF: 0, TF: 0 },
{ team: 'ENG', PF: 0, TF: 0 },
]
// prettier-ignore
const newTable: Table = [
{ team: 'ENG', P: 1, W: 1, D: 0, L: 0, PF: 7, PA: 0, DIFF: 7, TF: 1, TA: 0, BP: 0, PTS: 4 },
{ team: 'SCO', P: 0, W: 0, D: 0, L: 0, PF: 0, PA: 0, DIFF: 0, TF: 0, TA: 0, BP: 0, PTS: 0 },
]
const team1 = newTable[1]
const team2 = newTable[0]
expect(getTable(table, match)).toEqual([newTable, team1, team2])
})
})
describe('rules', () => {
// prettier-ignore
const tpl = { P: 0, W: 0, D: 0, L: 0, PF: 0, PA: 0, DIFF: 0, TF: 0, TA: 0, BP: 0, PTS: 0 }
describe('PgamesPlayed()', () => {
it('P - Increment number of games played', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 10, TF: 1 }
const team2Table = { team: 'ENG', ...tpl }
const team2Match = { team: 'ENG', PF: 3, TF: 0 }
const r = PgamesPlayed(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].P).toEqual(1)
expect(r[1].team).toEqual('ENG')
expect(r[1].P).toEqual(1)
})
})
describe('WgamesWon()', () => {
it('W - Increment number of games won, for winner', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 10, TF: 1 }
const team2Table = { team: 'ENG', ...tpl }
const team2Match = { team: 'ENG', PF: 3, TF: 0 }
const r = WgamesWon(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].W).toEqual(1)
expect(r[1].team).toEqual('ENG')
expect(r[1].W).toEqual(0)
})
})
describe('LgamesLost()', () => {
it('L - Increment number of games lost, for loser', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 10, TF: 1 }
const team2Table = { team: 'ENG', ...tpl }
const team2Match = { team: 'ENG', PF: 3, TF: 0 }
const r = LgamesLost(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].L).toEqual(0)
expect(r[1].team).toEqual('ENG')
expect(r[1].L).toEqual(1)
})
})
describe('DgamesDrawn()', () => {
it('D - Increment number of games drawn, for both teams', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 10, TF: 1 }
const team2Table = { team: 'ENG', ...tpl }
const team2Match = { team: 'ENG', PF: 10, TF: 1 }
const r = DgamesDrawn(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].D).toEqual(1)
expect(r[1].team).toEqual('ENG')
expect(r[1].D).toEqual(1)
})
})
describe('PFmatchPointsFor()', () => {
it('PF - Increment Match Points For, including penalty tries (1)', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 10, TF: 1 }
const team2Table = { team: 'ENG', ...tpl }
const team2Match = { team: 'ENG', PF: 5, TF: 0 }
const r = PFmatchPointsFor(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].PF).toEqual(10)
expect(r[1].team).toEqual('ENG')
expect(r[1].PF).toEqual(5)
})
it('PF - Increment Match Points For, including penalty tries (2)', () => {
const team1Table = { team: 'SCO', ...tpl, PF: 7 }
const team1Match = { team: 'SCO', PF: 10, TF: 1 }
const team2Table = { team: 'ENG', ...tpl, PF: 7 }
const team2Match = { team: 'ENG', PF: 5, TF: 0 }
const r = PFmatchPointsFor(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].PF).toEqual(17)
expect(r[1].team).toEqual('ENG')
expect(r[1].PF).toEqual(12)
})
})
describe('PAmatchPointsAgainst()', () => {
it('PA - Increment Match Points Against, including penalty tries', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 10, TF: 1 }
const team2Table = { team: 'ENG', ...tpl }
const team2Match = { team: 'ENG', PF: 5, TF: 0 }
const r = PAmatchPointsAgainst(
team1Table,
team1Match,
team2Table,
team2Match
)
expect(r[0].team).toEqual('SCO')
expect(r[0].PA).toEqual(5)
expect(r[1].team).toEqual('ENG')
expect(r[1].PA).toEqual(10)
})
})
describe('DIFFpointsDifference()', () => {
it('DIFF - Update Points Difference, Points For minus Points Against', () => {
const team1Table = { team: 'SCO', ...tpl, PF: 14, PA: 21 }
const team1Match = { team: 'SCO', PF: 10, TF: 1 }
const team2Table = { team: 'ENG', ...tpl, PF: 21, PA: 14 }
const team2Match = { team: 'ENG', PF: 10, TF: 1 }
const r = DIFFpointsDifference(
team1Table,
team1Match,
team2Table,
team2Match
)
expect(r[0].team).toEqual('SCO')
expect(r[0].DIFF).toEqual(-7)
expect(r[1].team).toEqual('ENG')
expect(r[1].DIFF).toEqual(7)
})
})
describe('TFtriesFor()', () => {
it('TF - Increment Tries For, including penalty tries', () => {
const team1Table = { team: 'SCO', ...tpl, TF: 2 }
const team1Match = { team: 'SCO', PF: 14, TF: 2 }
const team2Table = { team: 'ENG', ...tpl, TF: 1 }
const team2Match = { team: 'ENG', PF: 10, TF: 1 }
const r = TFtriesFor(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].TF).toEqual(4)
expect(r[1].team).toEqual('ENG')
expect(r[1].TF).toEqual(2)
})
})
describe('TAtriesAgainst()', () => {
it('TA - Increment Tries Against, including penalty tries', () => {
const team1Table = { team: 'SCO', ...tpl, TA: 2 }
const team1Match = { team: 'SCO', PF: 14, TF: 2 }
const team2Table = { team: 'ENG', ...tpl, TA: 1 }
const team2Match = { team: 'ENG', PF: 10, TF: 1 }
const r = TAtriesAgainst(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].TA).toEqual(3)
expect(r[1].team).toEqual('ENG')
expect(r[1].TA).toEqual(3)
})
})
describe('BPtryBonusPoints()', () => {
it('BP - Increment Bonus Points, for scoring four tries or more', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 38, TF: 4 }
const team2Table = { team: 'ENG', ...tpl, BP: 1, PTS: 1 }
const team2Match = { team: 'ENG', PF: 28, TF: 4 }
const r = BPtryBonusPoints(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].BP).toEqual(1)
expect(r[0].PTS).toEqual(1)
expect(r[1].team).toEqual('ENG')
expect(r[1].BP).toEqual(2)
expect(r[1].PTS).toEqual(2)
})
})
describe('BPlosingBonusPoints()', () => {
it('BP - Increment Bonus Points, for losing by a margin of seven points or fewer (1)', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 38, TF: 4 }
const team2Table = { team: 'ENG', ...tpl, BP: 1, PTS: 1 }
const team2Match = { team: 'ENG', PF: 28, TF: 4 }
const r = BPlosingBonusPoints(
team1Table,
team1Match,
team2Table,
team2Match
)
expect(r[0].team).toEqual('SCO')
expect(r[0].BP).toEqual(0)
expect(r[1].team).toEqual('ENG')
expect(r[1].BP).toEqual(1)
})
it('BP - Increment Bonus Points, for losing by a margin of seven points or fewer (2)', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 38, TF: 4 }
const team2Table = { team: 'ENG', ...tpl, BP: 1, PTS: 1 }
const team2Match = { team: 'ENG', PF: 35, TF: 5 }
const r = BPlosingBonusPoints(
team1Table,
team1Match,
team2Table,
team2Match
)
expect(r[0].team).toEqual('SCO')
expect(r[0].BP).toEqual(0)
expect(r[0].PTS).toEqual(0)
expect(r[1].team).toEqual('ENG')
expect(r[1].BP).toEqual(2)
expect(r[1].PTS).toEqual(2)
})
})
describe('PTScompetitionPoints()', () => {
it('PTS - Four points are awarded for a win', () => {
const team1Table = { team: 'SCO', ...tpl }
const team1Match = { team: 'SCO', PF: 38, TF: 4 }
const team2Table = { team: 'ENG', ...tpl }
const team2Match = { team: 'ENG', PF: 28, TF: 4 }
const r = PTScompetitionPoints(
team1Table,
team1Match,
team2Table,
team2Match
)
expect(r[0].team).toEqual('SCO')
expect(r[0].PTS).toEqual(4)
expect(r[1].team).toEqual('ENG')
expect(r[1].PTS).toEqual(0)
})
it('PTS - Two points are awarded for a draw', () => {
const team1Table = { team: 'SCO', ...tpl, PTS: 4 }
const team1Match = { team: 'SCO', PF: 7, TF: 1 }
const team2Table = { team: 'ENG', ...tpl }
const team2Match = { team: 'ENG', PF: 7, TF: 1 }
const r = PTScompetitionPoints(
team1Table,
team1Match,
team2Table,
team2Match
)
expect(r[0].team).toEqual('SCO')
expect(r[0].PTS).toEqual(6)
expect(r[1].team).toEqual('ENG')
expect(r[1].PTS).toEqual(2)
})
})
describe('PTSgrandSlam()', () => {
it('PTS - Three points are awarded for a grand slam', () => {
const team1Table = { team: 'SCO', ...tpl, P: 5, W: 5, PTS: 4 }
const team1Match = { team: 'SCO', PF: 7, TF: 1 }
const team2Table = { team: 'ENG', ...tpl, P: 5, W: 4 }
const team2Match = { team: 'ENG', PF: 7, TF: 1 }
const r = PTSgrandSlam(team1Table, team1Match, team2Table, team2Match)
expect(r[0].team).toEqual('SCO')
expect(r[0].PTS).toEqual(7)
expect(r[1].team).toEqual('ENG')
expect(r[1].PTS).toEqual(0)
})
})
})
export type Table = Array<TableRow>
export type TableRow = {
team: string
P: number // Played, number of games played
W: number // Won, number of games won
D: number // Draw, number of games drawn
L: number // Loss, number of games lost
PF: number // Match Points For, including penalty tries
PA: number // Match Points Against, including penalty tries
DIFF: number // Points Difference, Points For minus Points Against
TF: number // Tries For, including penalty tries
TA: number // Tries Against, including penalty tries
BP: number // Bonus Points, number of bonus points
PTS: number // Competition Points, number of championship points, a.k.a. Match Points
}
export type Match = Array<MatchOneSide>
export type MatchOneSide = {
team: string
PF: number // Points For, including penalty tries
TF: number // Tries, number of tries, including penalty tries
}
export const matches2table = (matches: Array<Match>) => {
const table: Table = matches.reduce(updateTable, [])
return orderTable(table)
}
/**
* Rules for scoring:
* - The Union that wins the Match shall be awarded four Competition Points
* or (if it scores four tries or more in the process) five Competition
* Points.
* - The Union that loses the Match shall be awarded no Competition Points
* or (if it scores four tries or more in the process or loses by a
* margin of seven points or fewer) one Match Point or (if it scores
* four tries or more in the process and loses by a margin of seven
* points or fewer) two Competition Points.
* - Unions that draw a Match shall each be awarded two Competition Points
* and any of them that scores four tries or more in the process shall be
* awarded a further one Match Point.
* - A Union that wins all five of its Matches (a “Grand Slam”) shall be
* awarded a further three Competition Points.
*/
export const updateTable = (table: Table, match: Match) => {
const [_table, team1Table, team2Table] = getTable(table, match)
const [team1Match, team2Match] = match
const rules = [
PgamesPlayed,
WgamesWon,
LgamesLost,
DgamesDrawn,
PFmatchPointsFor,
PAmatchPointsAgainst,
DIFFpointsDifference,
TFtriesFor,
TAtriesAgainst,
BPtryBonusPoints,
BPlosingBonusPoints,
PTScompetitionPoints,
PTSgrandSlam,
]
return rules.reduce((newTable: Table, ruleFn) => {
// ruleFn updates [team1Table,team2Table] in place
ruleFn(team1Table, team1Match, team2Table, team2Match)
return newTable
}, _table)
}
/**
* Rules for positioning:
* 1. The Union with the highest number of Match Points will be placed
* first and the other Unions placed in descending order according to
* the Match Points awarded.
* 2. If two or more Unions finish the Championship with the same number
* of Match Points, they will be placed according to the difference
* between the total points scored and the total points conceded on
* the fields of play in all Matches.
* 3. If any such Unions also have the same points difference, they will
* be placed according to the number of tries (including penalty
* tries) scored by each Union in all Matches.
* 4. If any such Unions have also scored the same number of tries (including penalty tries), they will be placed equally.
*/
export const orderTable = (table: Table) => {
const f = (a, b) => {
// PTS: higher number first
if (a.PTS < b.PTS) return 1
else if (a.PTS > b.PTS) return -1
// DIFF: higher number first
if (a.DIFF < b.DIFF) return 1
else if (a.DIFF > b.DIFF) return -1
// TF: higher number first
if (a.TF < b.TF) return 1
else if (a.TF > b.TF) return -1
return 0
}
return table.slice().sort(f)
}
export const getTable = (
table: Table,
match: Match
): [Table, TableRow, TableRow] => {
// prettier-ignore
const tableRowTemplate = { P: 0, W: 0, D: 0, L: 0, PF: 0, PA: 0, DIFF: 0, TF: 0, TA: 0, BP: 0, PTS: 0 }
const _table = table.slice()
let team1: TableRow | undefined = _table.find(
({ team }) => team === match[0].team
)
if (team1 === undefined) {
team1 = { team: match[0].team, ...tableRowTemplate }
_table.push(team1!)
}
let team2: TableRow | undefined = _table.find(
({ team }) => team === match[1].team
)
if (team2 === undefined) {
team2 = { team: match[1].team, ...tableRowTemplate }
_table.push(team2!)
}
return [_table, team1, team2]
}
export const PgamesPlayed = (
team1Table: TableRow,
_team1Match,
team2Table: TableRow,
_team2Match
): [TableRow, TableRow] => {
team1Table.P++
team2Table.P++
return [team1Table, team2Table]
}
export const WgamesWon = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
if (team1Match.PF > team2Match.PF) {
team1Table.W++
}
if (team2Match.PF > team1Match.PF) {
team2Table.W++
}
return [team1Table, team2Table]
}
export const LgamesLost = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
if (team1Match.PF < team2Match.PF) {
team1Table.L++
}
if (team2Match.PF < team1Match.PF) {
team2Table.L++
}
return [team1Table, team2Table]
}
export const DgamesDrawn = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
if (team1Match.PF === team2Match.PF) {
team1Table.D++
team2Table.D++
}
return [team1Table, team2Table]
}
export const PFmatchPointsFor = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
team1Table.PF += team1Match.PF
team2Table.PF += team2Match.PF
return [team1Table, team2Table]
}
export const PAmatchPointsAgainst = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
team1Table.PA += team2Match.PF
team2Table.PA += team1Match.PF
return [team1Table, team2Table]
}
export const DIFFpointsDifference = (
team1Table: TableRow,
_team1Match: MatchOneSide,
team2Table: TableRow,
_team2Match: MatchOneSide
): [TableRow, TableRow] => {
team1Table.DIFF = team1Table.PF - team1Table.PA
team2Table.DIFF = team2Table.PF - team2Table.PA
return [team1Table, team2Table]
}
export const TFtriesFor = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
team1Table.TF += team1Match.TF
team2Table.TF += team2Match.TF
return [team1Table, team2Table]
}
export const TAtriesAgainst = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
team1Table.TA += team2Match.TF
team2Table.TA += team1Match.TF
return [team1Table, team2Table]
}
export const BPtryBonusPoints = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
if (team1Match.TF >= 4) {
team1Table.BP++
team1Table.PTS++
}
if (team2Match.TF >= 4) {
team2Table.BP++
team2Table.PTS++
}
return [team1Table, team2Table]
}
export const BPlosingBonusPoints = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
if (team1Match.PF === team2Match.PF) return [team1Table, team2Table]
const team1HasLost = team1Match.PF < team2Match.PF
if (team1HasLost && team2Match.PF - team1Match.PF <= 7) {
team1Table.BP++
team1Table.PTS++
}
const team2HasLost = team2Match.PF < team1Match.PF
if (team2HasLost && team1Match.PF - team2Match.PF <= 7) {
team2Table.BP++
team2Table.PTS++
}
return [team1Table, team2Table]
}
export const PTScompetitionPoints = (
team1Table: TableRow,
team1Match: MatchOneSide,
team2Table: TableRow,
team2Match: MatchOneSide
): [TableRow, TableRow] => {
if (team1Match.PF > team2Match.PF) {
team1Table.PTS += 4
}
if (team2Match.PF > team1Match.PF) {
team2Table.PTS += 4
}
if (team1Match.PF === team2Match.PF) {
team1Table.PTS += 2
team2Table.PTS += 2
}
return [team1Table, team2Table]
}
export const PTSgrandSlam = (
team1Table: TableRow,
_team1Match: MatchOneSide,
team2Table: TableRow,
_team2Match: MatchOneSide
): [TableRow, TableRow] => {
if (team1Table.P === 5 && team1Table.W === 5) {
team1Table.PTS += 3
}
if (team2Table.P === 5 && team2Table.W === 5) {
team2Table.PTS += 3
}
return [team1Table, team2Table]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment