Last active
March 23, 2024 14:53
-
-
Save branneman/e3eb6ba8101e1003279cadcff8b656c5 to your computer and use it in GitHub Desktop.
Six Nations table calculator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
}) | |
}) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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