diff --git a/src/utilities/math/index.ts b/src/utilities/math/index.ts index c15878d..f17d495 100644 --- a/src/utilities/math/index.ts +++ b/src/utilities/math/index.ts @@ -1,5 +1,6 @@ import * as conversions from './conversions'; import * as fractions from './fractions'; import { gcd, gcf } from './gcf'; +import * as triangles from './triangles'; -export { gcf, gcd, fractions, conversions }; +export { gcf, gcd, fractions, conversions, triangles }; diff --git a/src/utilities/math/triangles.test.ts b/src/utilities/math/triangles.test.ts new file mode 100644 index 0000000..879324c --- /dev/null +++ b/src/utilities/math/triangles.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest'; +import * as math from '.'; + +describe('right', () => { + it('solves for triangle consistently', () => { + expect(math.triangles.right.solve({ angle: 30, opposite: 5 })).toStrictEqual({ + adjacent: 8.660254037844387, + angle: 30, + hypotenuse: 10.000000000000002, + opposite: 5, + }); + + expect( + math.triangles.right.solve({ angle: 30, adjacent: 8.660254037844387 }) + ).toStrictEqual({ + adjacent: 8.660254037844387, + angle: 30, + hypotenuse: 10, + opposite: 5, + }); + + expect(math.triangles.right.solve({ angle: 30, hypotenuse: 10 })).toStrictEqual({ + adjacent: 8.660254037844387, + angle: 30, + hypotenuse: 10, + opposite: 4.999999999999999, + }); + }); + + it('throws if 0 angle provided', () => { + expect(() => math.triangles.right.solve({ angle: 0, hypotenuse: 10 })).toThrow(); + }); + + it('throws if incorrect arguments were provided due to ignored type error', () => { + // @ts-ignore + expect(() => math.triangles.right.solve({ angle: 10 })).toThrow(); + }); +}); diff --git a/src/utilities/math/triangles.ts b/src/utilities/math/triangles.ts new file mode 100644 index 0000000..5064ee3 --- /dev/null +++ b/src/utilities/math/triangles.ts @@ -0,0 +1,104 @@ +import { dtr } from './conversions'; + +export type RightTriangle = { + /** Angle in degrees */ + angle: number; + /** opposite length */ + opposite: number; + /** adjacent length */ + adjacent: number; + /** hypotenuse length */ + hypotenuse: number; +}; + +export type SolveOptions = + | { + angle: number; + opposite: number; + adjacent?: never; + hypotenuse?: never; + } + | { + angle: number; + opposite?: never; + adjacent: number; + hypotenuse?: never; + } + | { + angle: number; + opposite?: never; + adjacent?: never; + hypotenuse: number; + }; + +/** Solves the right triangle based on the angle given and any one of the sides + * + * @param param0 + * @returns + */ +const solveRight = ({ angle, opposite, adjacent, hypotenuse }: SolveOptions): RightTriangle => { + if (angle <= 0) throw new Error(`Invalid value (${angle}) for 'angle'`); + + if (typeof hypotenuse === 'number') { + opposite = solveForOpposite({ angle, hypotenuse }); + adjacent = solveForAdjacent({ angle, hypotenuse }); + } else if (typeof opposite === 'number') { + adjacent = solveForAdjacent({ angle, opposite }); + hypotenuse = solveForHypotenuse({ angle, opposite }); + } else if (typeof adjacent === 'number') { + opposite = solveForOpposite({ angle, adjacent }); + hypotenuse = solveForHypotenuse({ angle, adjacent }); + } else { + throw new Error( + 'Incorrect arguments provided! expected opposite, adjacent, or hypotenuse to be a number' + ); + } + + return { + angle, + opposite, + adjacent, + hypotenuse, + }; +}; + +type OppositeSolveOptions = + | { angle: number; adjacent: number; hypotenuse?: never } + | { angle: number; adjacent?: never; hypotenuse: number }; + +const solveForOpposite = ({ angle, adjacent, hypotenuse }: OppositeSolveOptions): number => { + if (typeof hypotenuse === 'number') { + return Math.sin(dtr(angle)) * hypotenuse; + } + + return Math.tan(dtr(angle)) * adjacent; +}; + +type AdjacentSolveOptions = + | { angle: number; opposite: number; hypotenuse?: never } + | { angle: number; opposite?: never; hypotenuse: number }; + +const solveForAdjacent = ({ angle, opposite, hypotenuse }: AdjacentSolveOptions): number => { + if (typeof opposite === 'number') { + return opposite / Math.tan(dtr(angle)); + } + + return hypotenuse * Math.cos(dtr(angle)); +}; + +type HypotenuseSolveOptions = + | { angle: number; opposite: number; adjacent?: never } + | { angle: number; opposite?: never; adjacent: number }; + +const solveForHypotenuse = ({ angle, opposite, adjacent }: HypotenuseSolveOptions): number => { + if (typeof opposite === 'number') { + return opposite / Math.sin(dtr(angle)); + } + + return adjacent / Math.cos(dtr(angle)); +}; + +/** Functions for working with right triangles */ +const right = { solve: solveRight }; + +export { right };