Calculator simplification
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type {
|
||||
Metrics,
|
||||
Standard,
|
||||
import {
|
||||
Standards,
|
||||
StandardsConfig,
|
||||
type ActivityStandards,
|
||||
type Metrics,
|
||||
type Standard,
|
||||
type StandardsConfig,
|
||||
} from "$lib/services/calculator/main";
|
||||
import allStandardsRaw from "$lib/data/standards.json" assert { type: "json" };
|
||||
import {
|
||||
Activity,
|
||||
cmToIn,
|
||||
@@ -24,6 +26,17 @@
|
||||
|
||||
const { selected, allStandards }: Props = $props();
|
||||
|
||||
const allAnchorStandards = $derived.by(() => {
|
||||
const standardsCfg = structuredClone(selected.cfg);
|
||||
for (const activityCfg of Object.values(standardsCfg.activity)) {
|
||||
activityCfg.disableGeneration = true;
|
||||
}
|
||||
return new Standards(
|
||||
allStandardsRaw as ActivityStandards,
|
||||
standardsCfg,
|
||||
);
|
||||
});
|
||||
|
||||
const getLvlValue = (
|
||||
activity: Activity,
|
||||
standard: Standard,
|
||||
|
||||
@@ -4738,10 +4738,7 @@
|
||||
"attribute": "Endurance",
|
||||
"generators": [
|
||||
{
|
||||
"metric": "weight",
|
||||
"spread": 3,
|
||||
"step": 12,
|
||||
"ratio": "normal"
|
||||
"metric": "weight"
|
||||
}
|
||||
],
|
||||
"unit": "ms",
|
||||
@@ -5231,16 +5228,10 @@
|
||||
"attribute": "Power",
|
||||
"generators": [
|
||||
{
|
||||
"metric": "age",
|
||||
"spread": 4,
|
||||
"step": 10,
|
||||
"ratio": "inverse"
|
||||
"metric": "age"
|
||||
},
|
||||
{
|
||||
"metric": "weight",
|
||||
"spread": 3,
|
||||
"step": 12,
|
||||
"ratio": "inverse"
|
||||
"metric": "weight"
|
||||
}
|
||||
],
|
||||
"unit": "cm",
|
||||
@@ -5282,16 +5273,10 @@
|
||||
"attribute": "Agility",
|
||||
"generators": [
|
||||
{
|
||||
"metric": "weight",
|
||||
"spread": 3,
|
||||
"step": 12,
|
||||
"ratio": "normal"
|
||||
"metric": "weight"
|
||||
},
|
||||
{
|
||||
"metric": "age",
|
||||
"spread": 4,
|
||||
"step": 10,
|
||||
"ratio": "normal"
|
||||
"metric": "age"
|
||||
}
|
||||
],
|
||||
"unit": "ms",
|
||||
|
||||
@@ -10,11 +10,6 @@ import { Activity, Attribute, Gender, getAvgWeight, kgToLb, lbToKg, type Activit
|
||||
// Broad Jump:
|
||||
// https://nrpt.co.uk/training/tests/power/broad.htm
|
||||
|
||||
type LevelCalculatorConfig = {
|
||||
expandIters?: number;
|
||||
compressTo?: number;
|
||||
}
|
||||
|
||||
export type LevelCalculatorOutput = {
|
||||
player: number,
|
||||
attributes: Record<Attribute, number>
|
||||
@@ -36,10 +31,7 @@ export interface Standard {
|
||||
}
|
||||
|
||||
type Generator = {
|
||||
metric: NumberMetric,
|
||||
spread: number,
|
||||
step: number,
|
||||
ratio: "normal" | "inverse"
|
||||
metric: NumberMetric
|
||||
}
|
||||
|
||||
export type ActivityStandards = Record<Activity, {
|
||||
@@ -185,8 +177,8 @@ export type StandardsConfig = {
|
||||
weightModifier: number,
|
||||
weightSkew: number,
|
||||
ageModifier: number,
|
||||
disableGeneration: boolean,
|
||||
performanceSkew: number
|
||||
enableGeneration: boolean,
|
||||
difficultyModifier: number
|
||||
}>
|
||||
}
|
||||
|
||||
@@ -195,15 +187,18 @@ export class Standards {
|
||||
private activityStandards: ActivityStandards;
|
||||
|
||||
constructor(activityStandards: ActivityStandards, cfg?: Partial<StandardsConfig>) {
|
||||
// TODO: set these somewhere
|
||||
const defaultActivitiesConfig = Object.fromEntries(
|
||||
Object.values(Activity).map(activity => [
|
||||
activity,
|
||||
{
|
||||
disableGeneration: false,
|
||||
weightModifier: 0.1,
|
||||
enableGeneration: true,
|
||||
weightModifier: activity === Activity.BroadJump ?
|
||||
-0.1 :
|
||||
0.1,
|
||||
weightSkew: 0,
|
||||
ageModifier: 0.1,
|
||||
performanceSkew: 0
|
||||
difficultyModifier: 0
|
||||
},
|
||||
])
|
||||
) as Record<Activity, StandardsConfig["activity"][Activity]>;
|
||||
@@ -226,22 +221,24 @@ export class Standards {
|
||||
|
||||
// apply skew config
|
||||
for (const level in standard.levels) {
|
||||
standard.levels[level] = standard.levels[level] * (1 + this.cfg.activity[activity].performanceSkew)
|
||||
standard.levels[level] = standard.levels[level] * (1 + this.cfg.activity[activity].difficultyModifier)
|
||||
}
|
||||
}
|
||||
|
||||
// generate data
|
||||
const allGenerators = this.cfg.activity[activity].disableGeneration ?
|
||||
[] :
|
||||
this.activityStandards[activity].metadata.generators;
|
||||
const allGenerators = this.cfg.activity[activity].enableGeneration ?
|
||||
this.activityStandards[activity].metadata.generators :
|
||||
[];
|
||||
|
||||
const ageGenerators = allGenerators.filter(g => g.metric === "age");
|
||||
for (const ageGenerator of ageGenerators) {
|
||||
for (const gender of Object.values(Gender)) {
|
||||
const referenceAge = 50;
|
||||
const ageStep = 10;
|
||||
const referenceAge = 25;
|
||||
const medianAge = 50;
|
||||
|
||||
const minAge = referenceAge - (ageGenerator.step * ageGenerator.spread);
|
||||
const maxAge = referenceAge + (ageGenerator.step * ageGenerator.spread);
|
||||
const minAge = medianAge - (3 * ageStep);
|
||||
const maxAge = medianAge + (3 * ageStep);
|
||||
|
||||
const referenceStandard = this
|
||||
.byActivity(activity)
|
||||
@@ -264,9 +261,7 @@ export class Standards {
|
||||
if (!referenceStandard.levels[lvl]) continue;
|
||||
|
||||
const scalingExponent = this.cfg.activity[activity].ageModifier;
|
||||
const coefficient = ageGenerator.ratio === "inverse" ?
|
||||
referenceAge / currAge :
|
||||
currAge / referenceAge;
|
||||
const coefficient = currAge / referenceAge;
|
||||
newStandard.levels[lvl] = referenceStandard.levels[lvl] * (coefficient ** scalingExponent);
|
||||
}
|
||||
|
||||
@@ -278,7 +273,7 @@ export class Standards {
|
||||
if (!overlappingStandard)
|
||||
this.activityStandards[activity].standards.push(newStandard);
|
||||
|
||||
currAge += ageGenerator.step;
|
||||
currAge += ageStep;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,11 +286,11 @@ export class Standards {
|
||||
for (const weightGenerator of weightGenerators) {
|
||||
for (const gender of Object.values(Gender)) {
|
||||
for (const age of this.agesFor(activity, gender)) {
|
||||
const weightStep = 12;
|
||||
const referenceWeight = getAvgWeight(gender, age);
|
||||
|
||||
const skewRatio = this.cfg.activity[activity].weightSkew; // 0 = normal, 1 = max skew
|
||||
const minWeight = referenceWeight - (weightGenerator.step * weightGenerator.spread * (1 - skewRatio));
|
||||
const maxWeight = referenceWeight + (weightGenerator.step * weightGenerator.spread * (1 + skewRatio));
|
||||
const minWeight = referenceWeight - (3 * weightStep);
|
||||
const maxWeight = referenceWeight + (3 * weightStep);
|
||||
|
||||
const referenceStandard = this
|
||||
.byActivity(activity)
|
||||
@@ -317,10 +312,8 @@ export class Standards {
|
||||
for (const lvl in referenceStandard.levels) {
|
||||
if (!referenceStandard.levels[lvl]) continue;
|
||||
|
||||
const scalingExponent = this.cfg.activity[activity].weightModifier;
|
||||
const coefficient = weightGenerator.ratio === "inverse" ?
|
||||
referenceWeight / currWeight :
|
||||
currWeight / referenceWeight;
|
||||
const scalingExponent = this.cfg.activity[activity].weightModifier
|
||||
const coefficient = currWeight / referenceWeight;
|
||||
|
||||
newStandard.levels[lvl] = referenceStandard.levels[lvl] * (coefficient ** scalingExponent);
|
||||
}
|
||||
@@ -333,7 +326,7 @@ export class Standards {
|
||||
if (!overlappingStandard)
|
||||
this.activityStandards[activity].standards.push(newStandard);
|
||||
|
||||
currWeight += weightGenerator.step;
|
||||
currWeight += weightStep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,43 +67,6 @@ export const msToTime = (ms: number, includeMs = false): string => {
|
||||
|
||||
export const range = (length: number) => Array.from({ length: length }, (_, i) => i + 1);
|
||||
|
||||
// export const getAvgWeight = (gender: Gender, age: number) => {
|
||||
// type AvgWeightData = {
|
||||
// metadata: {
|
||||
// unit: StandardUnit
|
||||
// },
|
||||
// weights: Metrics[]
|
||||
// }
|
||||
// let avgWeightData = avgWeightDataRaw as AvgWeightData;
|
||||
//
|
||||
// const avgWeights = avgWeightData.weights.filter(a => a.gender === gender);
|
||||
//
|
||||
// let lower = avgWeights[0]; // best <= target
|
||||
// let upper = avgWeights[0]; // best >= target
|
||||
// let hasLower = false, hasUpper = false;
|
||||
//
|
||||
// for (const avg of avgWeights) {
|
||||
// if (avg.age <= age) {
|
||||
// if (!hasLower || avg.age > lower.age) { lower = avg; hasLower = true; }
|
||||
// }
|
||||
// if (avg.age >= age) {
|
||||
// if (!hasUpper || avg.age < upper.age) { upper = avg; hasUpper = true; }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // clamp to ends if one side missing
|
||||
// if (!hasLower) lower = avgWeights[0];
|
||||
// if (!hasUpper) upper = avgWeights[avgWeights.length - 1];
|
||||
//
|
||||
// // lerp
|
||||
// let ageRatio = upper === lower ? 1 : (age - lower.age) / (upper.age - lower.age);
|
||||
// ageRatio = Math.max(0, Math.min(1, ageRatio));
|
||||
// const interpolatedAvg = lower.weight + (upper.weight - lower.weight) * ageRatio;
|
||||
//
|
||||
// return interpolatedAvg;
|
||||
// }
|
||||
|
||||
|
||||
export const getAvgWeight = (gender: Gender, age: number) => {
|
||||
type AvgWeightData = {
|
||||
metadata: {
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
|
||||
let activeTab = $state("standards");
|
||||
|
||||
let inputSelected = $state({
|
||||
const defaultStandards = new Standards(
|
||||
allStandardsRaw as ActivityStandards,
|
||||
);
|
||||
|
||||
let input = $state({
|
||||
activity: Activity.BenchPress,
|
||||
metrics: {
|
||||
gender: Gender.Male,
|
||||
@@ -21,51 +25,57 @@
|
||||
},
|
||||
cfg: {
|
||||
global: {
|
||||
maxLevel: "100",
|
||||
maxLevel: String(defaultStandards.cfg.global.maxLevel),
|
||||
},
|
||||
activity: Object.fromEntries(
|
||||
Object.values(Activity).map((activity) => [
|
||||
activity,
|
||||
{
|
||||
enableGeneration: true,
|
||||
weightModifier: ".1",
|
||||
// weightSkew: "0",
|
||||
ageModifier: ".1",
|
||||
performanceSkew: "0",
|
||||
},
|
||||
]),
|
||||
Object.values(Activity).map((activity) => {
|
||||
return [
|
||||
activity,
|
||||
{
|
||||
enableGeneration:
|
||||
defaultStandards.cfg.activity[activity]
|
||||
.enableGeneration,
|
||||
weightAdvantage: String(
|
||||
defaultStandards.cfg.activity[activity]
|
||||
.weightModifier,
|
||||
),
|
||||
ageModifier: String(
|
||||
defaultStandards.cfg.activity[activity]
|
||||
.ageModifier,
|
||||
),
|
||||
difficultyModifier: String(
|
||||
defaultStandards.cfg.activity[activity]
|
||||
.difficultyModifier,
|
||||
),
|
||||
},
|
||||
];
|
||||
}),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const selected = $derived({
|
||||
activity: inputSelected.activity,
|
||||
activity: input.activity,
|
||||
metrics: {
|
||||
gender: inputSelected.metrics.gender,
|
||||
age: +inputSelected.metrics.age,
|
||||
weight: lbToKg(+inputSelected.metrics.weight),
|
||||
gender: input.metrics.gender,
|
||||
age: +input.metrics.age,
|
||||
weight: lbToKg(+input.metrics.weight),
|
||||
} as Metrics,
|
||||
cfg: {
|
||||
global: {
|
||||
maxLevel: +inputSelected.cfg.global.maxLevel,
|
||||
maxLevel: +input.cfg.global.maxLevel,
|
||||
},
|
||||
activity: Object.fromEntries(
|
||||
Object.values(Activity).map((activity) => [
|
||||
activity,
|
||||
{
|
||||
weightModifier:
|
||||
+inputSelected.cfg.activity[activity]
|
||||
.weightModifier,
|
||||
weightSkew: 0,
|
||||
// +inputSelected.cfg.activity[activity].weightSkew,
|
||||
ageModifier:
|
||||
+inputSelected.cfg.activity[activity].ageModifier,
|
||||
disableGeneration:
|
||||
!inputSelected.cfg.activity[activity]
|
||||
.enableGeneration,
|
||||
performanceSkew:
|
||||
+inputSelected.cfg.activity[activity]
|
||||
.performanceSkew,
|
||||
+input.cfg.activity[activity].weightAdvantage,
|
||||
ageModifier: +input.cfg.activity[activity].ageModifier,
|
||||
enableGeneration:
|
||||
input.cfg.activity[activity].enableGeneration,
|
||||
difficultyModifier:
|
||||
+input.cfg.activity[activity].difficultyModifier,
|
||||
},
|
||||
]),
|
||||
),
|
||||
@@ -78,16 +88,16 @@
|
||||
|
||||
$effect(() => {
|
||||
console.log(selected);
|
||||
if (!inputSelected.metrics.age) inputSelected.metrics.weight = "";
|
||||
if (!input.metrics.age) input.metrics.weight = "";
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const savedParameters = localStorage.getItem("parameters");
|
||||
if (savedParameters) inputSelected = JSON.parse(savedParameters);
|
||||
if (savedParameters) input = JSON.parse(savedParameters);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
localStorage.setItem("parameters", JSON.stringify(inputSelected));
|
||||
localStorage.setItem("parameters", JSON.stringify(input));
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -110,7 +120,7 @@
|
||||
min="1"
|
||||
max="100"
|
||||
placeholder="5"
|
||||
bind:value={inputSelected.cfg.global.maxLevel}
|
||||
bind:value={input.cfg.global.maxLevel}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -126,7 +136,7 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={
|
||||
inputSelected.cfg.activity[selected.activity]
|
||||
input.cfg.activity[selected.activity]
|
||||
.enableGeneration
|
||||
}
|
||||
class="toggle toggle-lg m-auto"
|
||||
@@ -142,11 +152,11 @@
|
||||
step=".05"
|
||||
placeholder=".1"
|
||||
bind:value={
|
||||
inputSelected.cfg.activity[selected.activity]
|
||||
.weightModifier
|
||||
input.cfg.activity[selected.activity]
|
||||
.weightAdvantage
|
||||
}
|
||||
disabled={selected.cfg.activity[selected.activity]
|
||||
.disableGeneration}
|
||||
disabled={!selected.cfg.activity[selected.activity]
|
||||
.enableGeneration}
|
||||
/>
|
||||
</label>
|
||||
<!-- <label> -->
|
||||
@@ -161,8 +171,8 @@
|
||||
<!-- inputSelected.cfg.activity[selected.activity] -->
|
||||
<!-- .weightSkew -->
|
||||
<!-- } -->
|
||||
<!-- disabled={selected.cfg.activity[selected.activity] -->
|
||||
<!-- .disableGeneration} -->
|
||||
<!-- disabled={!selected.cfg.activity[selected.activity] -->
|
||||
<!-- .enableGeneration} -->
|
||||
<!-- /> -->
|
||||
<!-- </label> -->
|
||||
<label>
|
||||
@@ -174,11 +184,11 @@
|
||||
max="1"
|
||||
step=".05"
|
||||
bind:value={
|
||||
inputSelected.cfg.activity[selected.activity]
|
||||
.performanceSkew
|
||||
input.cfg.activity[selected.activity]
|
||||
.difficultyModifier
|
||||
}
|
||||
disabled={selected.cfg.activity[selected.activity]
|
||||
.disableGeneration}
|
||||
disabled={!selected.cfg.activity[selected.activity]
|
||||
.enableGeneration}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
@@ -190,11 +200,11 @@
|
||||
max="1"
|
||||
step=".05"
|
||||
bind:value={
|
||||
inputSelected.cfg.activity[selected.activity]
|
||||
input.cfg.activity[selected.activity]
|
||||
.ageModifier
|
||||
}
|
||||
disabled={selected.cfg.activity[selected.activity]
|
||||
.disableGeneration}
|
||||
disabled={!selected.cfg.activity[selected.activity]
|
||||
.enableGeneration}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -213,7 +223,7 @@
|
||||
<select
|
||||
class="select w-full"
|
||||
name="activities"
|
||||
bind:value={inputSelected.activity}
|
||||
bind:value={input.activity}
|
||||
>
|
||||
{#each Object.values(Activity) as activity}
|
||||
<option value={activity}>
|
||||
@@ -229,7 +239,7 @@
|
||||
<select
|
||||
class="select w-full"
|
||||
name="genders"
|
||||
bind:value={inputSelected.metrics.gender}
|
||||
bind:value={input.metrics.gender}
|
||||
>
|
||||
{#each Object.values(Gender) as gender}
|
||||
<option value={gender}>{gender}</option>
|
||||
@@ -245,7 +255,7 @@
|
||||
min="0"
|
||||
max="100"
|
||||
placeholder="18"
|
||||
bind:value={inputSelected.metrics.age}
|
||||
bind:value={input.metrics.age}
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -259,7 +269,7 @@
|
||||
placeholder={selected.metrics.age
|
||||
? "170"
|
||||
: "Requires age"}
|
||||
bind:value={inputSelected.metrics.weight}
|
||||
bind:value={input.metrics.weight}
|
||||
disabled={!selected.metrics.age}
|
||||
/>
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user