From 4ac4a7f991bb0e8fb3cd0dc412f1eab1a5895696 Mon Sep 17 00:00:00 2001 From: Flavio Di Natale Date: Wed, 18 Sep 2024 12:21:42 +0200 Subject: [PATCH] Draft Polish scale implementaion --- src/GradeScale.ts | 3 +- src/__tests__/scales/polish.ts | 97 ++++++++++++++++++++++++++++++++++ src/index.ts | 42 +++++++++++++-- src/scales/index.ts | 5 +- src/scales/polish.ts | 79 +++++++++++++++++++++++++++ 5 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 src/__tests__/scales/polish.ts create mode 100644 src/scales/polish.ts diff --git a/src/GradeScale.ts b/src/GradeScale.ts index 01e016b..12e3cda 100644 --- a/src/GradeScale.ts +++ b/src/GradeScale.ts @@ -26,7 +26,8 @@ export const GradeScales = { EWBANK: 'ewbank', SAXON: 'saxon', NORWEGIAN: 'norwegian', - BRAZILIAN_CRUX: 'brazilian_crux' + BRAZILIAN_CRUX: 'brazilian_crux', + POLISH: 'polish' } as const export type GradeScalesTypes = typeof GradeScales[keyof typeof GradeScales] diff --git a/src/__tests__/scales/polish.ts b/src/__tests__/scales/polish.ts new file mode 100644 index 0000000..eaa38a2 --- /dev/null +++ b/src/__tests__/scales/polish.ts @@ -0,0 +1,97 @@ +// import { GradeBands } from '../../GradeBands' +import { Polish } from '../../scales' + +describe('Polish', () => { + describe('isPolish', () => { + test('VI', () => { + expect(Polish.isType('VI')) + }) + }) + // describe('Get Score', () => { + // test('9a > 5c', () => { + // const lowGrade = Polish.getScore('5c') + // const highGrade = Polish.getScore('9a') + // expect(highGrade[0]).toBeGreaterThan(lowGrade[1]) + // }) + + // test('1c > 1a+', () => { + // const highGrade = Polish.getScore('1c') + // const lowGrade = Polish.getScore('1a+') + // expect(highGrade[0]).toBeGreaterThan(lowGrade[1]) + // }) + + // test('1a/1a+ > 1a, one grade away', () => { + // const highGrade = Polish.getScore('1a/1a+') + // const lowGrade = Polish.getScore('1a') + // expect(highGrade[0] < lowGrade[1] && highGrade[0] > lowGrade[0]) + // expect(highGrade[1]).toBeGreaterThan(lowGrade[1]) + // }) + + // test('4a > 3c+/4a, one grade away', () => { + // const highGrade = Polish.getScore('4a') + // const lowGrade = Polish.getScore('3c+/4a') + // expect(highGrade[0] < lowGrade[1] && highGrade[0] > lowGrade[0]) + // expect(highGrade[1]).toBeGreaterThan(lowGrade[1]) + // }) + // }) + + // describe('invalid grade format', () => { + // jest.spyOn(console, 'warn').mockImplementation() + // beforeEach(() => { + // jest.clearAllMocks() + // }) + // test('extra plus modifier', () => { + // const invalidGrade = Polish.getScore('5a++') + // expect(console.warn).toHaveBeenCalledWith('Unexpected grade format: 5a++ for grade scale polish') + // expect(invalidGrade).toEqual(-1) + // }) + // test('invalid minus modifier', () => { + // const invalidGrade = Polish.getScore('5a-') + // expect(console.warn).toHaveBeenCalledWith('Unexpected grade format: 5a- for grade scale polish') + // expect(invalidGrade).toEqual(-1) + // }) + // test('extra slash grade', () => { + // const invalidGrade = Polish.getScore('5a/5a+/5b+') + // expect(console.warn).toHaveBeenCalledWith('Unexpected grade format: 5a/5a+/5b+ for grade scale polish') + // expect(invalidGrade).toEqual(-1) + // }) + // test('extra slash', () => { + // const invalidGrade = Polish.getScore('5a/') + // expect(console.warn).toHaveBeenCalledWith('Unexpected grade format: 5a/ for grade scale polish') + // expect(invalidGrade).toEqual(-1) + // }) + // test('not Polish scale', () => { + // const invalidGrade = Polish.getScore('v11') + // expect(console.warn).toHaveBeenCalledWith('Unexpected grade format: v11 for grade scale polish') + // expect(invalidGrade).toEqual(-1) + // }) + // }) + + // describe('Get Grade', () => { + // test('bottom of range', () => { + // expect(Polish.getGrade(0)).toBe('1a') + // }) + + // test('top of range', () => { + // expect(Polish.getGrade(1000)).toBe('9c+') + // }) + + // test('single score provided', () => { + // expect(Polish.getGrade(34)).toBe('3c+') + // expect(Polish.getGrade(34.5)).toBe('3c+') + // expect(Polish.getGrade(35)).toBe('3c+') + // }) + // test('range of scores provided', () => { + // expect(Polish.getGrade([0.5, 2])).toBe('1a/1a+') + // expect(Polish.getGrade([8, 12])).toBe('1c/2a') + // expect(Polish.getGrade([16, 17])).toBe('2b') + // }) + // }) + + // describe('Get Grade Band', () => { + // test('gets Gradeband', () => { + // expect(Polish.getGradeBand('1a')).toEqual(GradeBands.BEGINNER) + // expect(Polish.getGradeBand('9c+')).toEqual(GradeBands.EXPERT) + // }) + // }) +}) diff --git a/src/index.ts b/src/index.ts index a900ae2..73bcdc2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import { convertGrade } from './GradeParser' import { GradeBands, GradeBandTypes } from './GradeBands' -import { AI, Aid, Ewbank, Font, French, Norwegian, Saxon, UIAA, VScale, WI, YosemiteDecimal, BrazilianCrux } from './scales' +import { AI, Aid, Ewbank, Font, French, Norwegian, Saxon, UIAA, VScale, WI, YosemiteDecimal, BrazilianCrux, Polish } from './scales' // Free Climbing Grades // YDS @@ -261,6 +261,39 @@ const NORWAY_ARRAY = [ '12+' ] +// TODO: check this is acutally needed and where it is used +// seems recent changes for the brasilian scale did not introduce this change +const POLISH_ARRAY = [ + 'I', + 'II', + 'III', + 'IV', + 'IV+', + 'V-', + 'V', + 'V+', + 'VI', + 'VI+', + 'VI.1', + 'VI.1+', + 'VI.2', + 'VI.2+', + 'VI.3', + 'VI.3+', + 'VI.4', + 'VI.4+', + 'VI.5', + 'VI.5+', + 'VI.6', + 'VI.6+', + 'VI.7', + 'VI.7+', + 'VI.8', + 'VI.8+', + 'VI.9', + 'VI.9+' +] + const CLASS_ARRAY = ['Class 1', 'Class 2', 'Class 3', 'Class 4', 'Class 5'] export const protection = ['G', 'PG', 'PG13', 'R', 'X'] @@ -280,7 +313,9 @@ export const freeClimbing = { Ewbank: EWBANK_ARRAY, Saxon: SAXON_ARRAY, Norwegian: NORWAY_ARRAY, - BrazilianCrux: BrazilianCrux.grades + // TODO: check why this differ + BrazilianCrux: BrazilianCrux.grades, + Polish: POLISH_ARRAY }, community: {} } @@ -312,5 +347,6 @@ export { VScale, WI, YosemiteDecimal, - BrazilianCrux + BrazilianCrux, + Polish } diff --git a/src/scales/index.ts b/src/scales/index.ts index 7197fed..0a078e5 100644 --- a/src/scales/index.ts +++ b/src/scales/index.ts @@ -9,9 +9,10 @@ import AI from './ai' import Aid from './aid' import WI from './wi' import BrazilianCrux from './brazilian' +import Polish from './polish' import UIAA from './uiaa' import GradeScale, { GradeScales } from '../GradeScale' -export { Aid, VScale, Font, YosemiteDecimal, French, Saxon, UIAA, Ewbank, AI, WI, Norwegian, BrazilianCrux } +export { Aid, VScale, Font, YosemiteDecimal, French, Saxon, UIAA, Ewbank, AI, WI, Norwegian, BrazilianCrux, Polish } export interface Boulder { score: number @@ -28,6 +29,7 @@ export interface Route { saxon: string norwegian: string brazilian: string + polish: string } export interface IceGrade { @@ -54,6 +56,7 @@ GradeScale | null [GradeScales.SAXON]: Saxon, [GradeScales.NORWEGIAN]: Norwegian, [GradeScales.BRAZILIAN_CRUX]: BrazilianCrux, + [GradeScales.POLISH]: Polish, [GradeScales.AI]: AI, [GradeScales.WI]: WI, [GradeScales.AID]: Aid diff --git a/src/scales/polish.ts b/src/scales/polish.ts new file mode 100644 index 0000000..7dc1f79 --- /dev/null +++ b/src/scales/polish.ts @@ -0,0 +1,79 @@ +import GradeScale, { findScoreRange, getAvgScore, GradeScales, Tuple, getRoundedScoreTuple } from '../GradeScale' +import routes from '../data/routes.json' +import { Route } from '.' +import { GradeBandTypes, routeScoreToBand } from '../GradeBands' + +// TODO: set this regexp +const polishGradeRegex = /^([1-9][a-c][+]?){1}(?:(\/)([1-9][a-c][+]?))?$/i +// Supports 1a -> 9c+, slash grades i.e. 5a/5a+ or 6a+/6b +// NOTE: this currently assumes "incorrect" slash grades follows the normal pattern +// i.e. 6b+/5a => 6b+/6c +const isPolish = (grade: string): RegExpMatchArray | null => grade.match(polishGradeRegex) + +const PolishScale: GradeScale = { + displayName: 'Polish Scale', + name: GradeScales.POLISH, + offset: 1000, + allowableConversionType: [GradeScales.YDS, GradeScales.EWBANK, GradeScales.SAXON], + isType: (grade: string): boolean => { + if (isPolish(grade) === null) { + return false + } + return true + }, + getScore: (grade: string): number | Tuple => { + return getScore(grade) + }, + getGrade: (score: number | Tuple): string => { + const validateScore = (score: number): number => { + const validScore = Number.isInteger(score) ? score : Math.ceil(score) + return Math.min(Math.max(0, validScore), routes.length - 1) + } + + if (typeof score === 'number') { + return routes[validateScore(score)].polish + } + + const low: string = routes[validateScore(score[0])].polish + const high: string = routes[validateScore(score[1])].polish + if (low === high) return low + return `${low}/${high}` + }, + getGradeBand: (grade: string): GradeBandTypes => { + const score = getScore(grade) + return routeScoreToBand(getAvgScore(score)) + } +} + +const getScore = (grade: string): number | Tuple => { + const parse = isPolish(grade) + if (parse == null) { + console.warn(`Unexpected grade format: ${grade} for grade scale polish`) + return -1 + } + const [wholeMatch, basicGrade, slash] = parse + const basicScore = findScoreRange((r: Route) => { + return r.polish === basicGrade + }, routes) + + if (wholeMatch !== basicGrade) { + // 5a/5a+ + let otherGrade + if (slash !== null) { + otherGrade = (typeof basicScore === 'number' ? basicScore : basicScore[1]) + 1 + } + if (otherGrade !== undefined) { + const nextGrade = findScoreRange( + (r: Route) => r.polish.toLowerCase() === routes[otherGrade].polish.toLowerCase(), + routes + ) + const basicAvg = getAvgScore(basicScore) + const nextGradeAvg = getAvgScore(nextGrade) + const tuple = getRoundedScoreTuple(basicAvg, nextGradeAvg) + return tuple + } + } + return basicScore +} + +export default PolishScale