import {useSnackbar} from "notistack"; import {useEffect, useState} from "react"; import {BiasParams, CarSetupParams} from "../../consts/params"; import {arrayFloatEqual, biasToSetup, eps, nearestSetup, randomSetup, setupToBias} from "../../libs/setup"; import axios from "axios"; import { Button, Chip, Container, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, FormControl, Grid, InputLabel, MenuItem, Paper, Select, Slider, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography } from "@mui/material"; import {trackMap, tracks} from "../../consts/tracks"; import Image from "next/image"; import {DataGrid} from "@mui/x-data-grid"; import {Delete, OpenInNew} from "@mui/icons-material"; import dynamic from "next/dynamic"; const feedbackColors = { optimal: "info", great: "primary", good: "white", bad: "error", "bad+": "error", "bad-": "error", } export function Calculator({ target, preset }) { const { enqueueSnackbar } = useSnackbar(); const [isValidSetup, setIsValidSetup] = useState([true, true, true, true, true]); const [lastCarSetup, setLastCarSetup] = useState([0.5, 0.5, 0.5, 0.5, 0.5]); const [carSetup, _setCarSetup] = useState([0.5, 0.5, 0.5, 0.5, 0.5]); const [biasParam, _setBiasParam] = useState([0.5, 0.5, 0.5, 0.5, 0.5]); const [prevCarSetup, setPrevCarSetup] = useState([0.5, 0.5, 0.5, 0.5, 0.5]); const [prevBiasParam, setPrevBiasParam] = useState([0.5, 0.5, 0.5, 0.5, 0.5]); const [carSetupList, setCarSetupList] = useState([]); const [biasParamText, setBiasParamText] = useState([0.5, 0.5, 0.5, 0.5, 0.5]); const [feedback, setFeedback] = useState([[], [], [], [], []]); const [previousRuns, setPreviousRuns] = useState([]); const [possibleSetups, setPossibleSetups] = useState(1012095); const [track, setTrack] = useState("XX"); const [loaded, setLoaded] = useState(false); const [openClearFeedback, setOpenClearFeedback] = useState(false); const getIdentifier = () => { if (typeof window !== "undefined") { if (!localStorage["identifier"] || localStorage["identifier"].length !== 8) { localStorage["identifier"] = Array.from(Array(8)).map( () => String.fromCharCode(Math.floor(Math.random() * 26) + 65) ).join(""); } return localStorage["identifier"] + "-" + target.toUpperCase() } return "UNKNOWN" } useEffect(() => { try { const { track, isValidSetup, carSetup, biasParam, prevCarSetup, prevBiasParam, feedback, previousRuns, } = JSON.parse(localStorage[target]) setFeedback(feedback); _setBiasParam(biasParam); _setCarSetup(carSetup); setPrevCarSetup(prevCarSetup); setPrevBiasParam(prevBiasParam); setPreviousRuns(previousRuns || []); setTrack(track); setIsValidSetup(isValidSetup); console.log("loaded", target); } catch (e) { console.log(e); setPreviousRuns([]); setTrack("XX"); } setLoaded(true); }, [target]) if (loaded && typeof window !== "undefined") { localStorage.setItem(target, JSON.stringify({ isValidSetup, carSetup, biasParam, prevCarSetup, prevBiasParam, feedback, track, previousRuns, })); } const setBiasParam = (e, _idx=-1) => { _setBiasParam(e); setBiasParamText( biasParamText.map( (v, idx) => idx === _idx ? biasParamText[idx] : e[idx].toFixed(6) ) ); } const setCarSetup = (e) => { _setCarSetup(e); setIsValidSetup(CarSetupParams.map(p => { if (e[p.index] < -1e-6 || e[p.index] >= 1+1e-6) { return false; } const roundValue = e[p.index] * (p.max - p.min) / p.step; return Math.abs(Math.round(roundValue) - roundValue) <= 1e-6; })); } const findNearest = () => { setPrevCarSetup(carSetup); setPrevBiasParam(biasParam); const {setup, possibleSetups, lowestRuleBreak, possibleSetupList} = nearestSetup(biasParam, feedback); if (setup) { if (lowestRuleBreak > 0) { enqueueSnackbar( 'Unable to find a valid setup matching all feedbacks. This is the closest we could get.', { variant: "warning" } ); setPossibleSetups(0); } else { /* what */ // if (possibleSetups === 1) { // axios.post(`/api/report_vals`, { // uid: getIdentifier(), // track, // values: setup, // }); // } setPossibleSetups(possibleSetups); } setBiasParam(setupToBias(setup)); setCarSetup(setup); setCarSetupList(possibleSetupList); } else { enqueueSnackbar( 'Unable to find a valid setup matching all feedbacks. Try deleting some feedbacks.', { variant: "error" } ); } if (typeof window !== "undefined") { localStorage.c = Number(localStorage.c || 0) + 1 } } const clearAll = () => { setFeedback([[], [], [], [], []]); setPreviousRuns([]); setLastCarSetup(carSetup); } const clearFeedbacks = () => { setFeedback([[], [], [], [], []]); setLastCarSetup(carSetup); // setPreviousRuns([]); } const createFeedback = (row, biasValue, v) => { const matchedRuns = previousRuns.filter(x => ( x.track === track && arrayFloatEqual(x.carSetup, carSetup) )) if (matchedRuns.length) { setPreviousRuns( previousRuns.map(x => x.id === matchedRuns[0].id ? { ...x, ["feedback_" + row.index]: { value: biasValue, timestamp: +new Date(), feedback: v }, } : x) ) } else { setPreviousRuns([ { track, carSetup: JSON.parse(JSON.stringify(carSetup)), ["feedback_" + row.index]: { value: biasValue, timestamp: +new Date(), feedback: v }, id: +new Date(), }, ...previousRuns ]) } setLastCarSetup(carSetup) setFeedback( feedback.map((x, idx) => idx === row.index ? [ ...x.filter(x => x.value !== biasValue), { value: biasValue, timestamp: +new Date(), feedback: v } ]: x) ) if (v === "optimal" && Number(localStorage.c) > 6 && Object.keys(preset).length) { axios.post(`/api/report`, { uid: getIdentifier(), track, value: biasValue, feedback: v, index: row.index, }); } } const loadPreset = () => { if (preset[track]) { const {setup} = nearestSetup( preset[track], [[], [], [], [], []] ); setCarSetup(setup); setBiasParam(setupToBias(setup)); } } try { return ( { setOpenClearFeedback(false); }} aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" > Clear Feedbacks? Since you moved to a new track, do you need to load the preset value and clear all previous feedbacks? Track Select: Setup Values Compare { CarSetupParams.map(row => { let carSetupDiff = carSetup[row.index] - lastCarSetup[row.index]; if (Math.abs(carSetupDiff) < eps) { carSetupDiff = 0; } return ( {row.name} 1 || carSetup[row.index] < 0) ? "error" : (isValidSetup[row.index] ? "primary" : "warning") } step={row.step / (row.max - row.min)} max={Math.max(1, carSetup[row.index])} min={Math.min(0, carSetup[row.index])} valueLabelFormat={v => row.render(v * (row.max - row.min) + row.min)} valueLabelDisplay="on" value={carSetup[row.index]} onChange={(e, value) => { const setup = carSetup.map((x, idx) => idx === row.index ? ( Math.round(value * 560) / 560 ): x); setCarSetup(setup) setBiasParam(setupToBias(setup)) setIsValidSetup( isValidSetup.map((x, idx) => idx === row.index ? ( carSetup[row.index] >= 0 && carSetup[row.index] <= 1 ) : x) ) }} /> 0 ? "#ff6383" : carSetupDiff < 0 ? "#76ff03" : "white" }}>{carSetupDiff > 0 ? "▲" : carSetupDiff < 0 ? "▼" : ""} { row.render(carSetup[row.index] * (row.max - row.min) + row.min) } { Math.abs(carSetupDiff) > eps && ( Prev: { row.render(lastCarSetup[row.index] * (row.max - row.min) + row.min) } ) } ) }) }
{possibleSetups} Setups Possible
{/* */} Feedback Bias Value { BiasParams.map(row => { const feedbacks = feedback[row.index]; const biasValue = biasParam[row.index]; const k = row.name + ":" + target; let currentFeedback = ""; for(const fb of feedbacks) { if (fb.value === biasValue) { currentFeedback = fb.feedback; } } return [( {row.name} v.toFixed(6)} valueLabelDisplay="on" value={biasParam[row.index]} onChange={(e, value) => { const bias = biasParam.map((x, idx) => idx === row.index ? value : x); setBiasParam(bias) setCarSetup(biasToSetup(bias)) }} /> { const val = e.target.value; const nVal = Number(val); if (0 <= nVal && nVal <= 1) { const b = biasParam.map((x, idx) => idx === row.index ? nVal : x); setBiasParam(b) setCarSetup(biasToSetup(b)) } setBiasParamText( biasParamText.map((x, idx) => idx === row.index ? val : x) ) } } /> ),( { feedbacks.sort( (x, y) => x.value - y.value ).map((f, _idx) => ( { const bias = biasParam.map((x, idx) => idx === row.index ? f.value : x); setBiasParam(bias) setCarSetup(biasToSetup(bias)) }} onDelete={() => { setFeedback( feedback.map((x, idx) => idx === row.index ? x.filter(x => x.value !== f.value) : x ) ) }} /> )) } )]; }).flat() }
{ const biasParams = setupToBias(x.arr); return {...x, biasParams, id: id + 1} }) ]} columns={[ { field: 'id', headerName: 'Setup #', renderCell : ({ row, value }) => }, { field: 'diff', headerName: '%', valueGetter: ({ value }) => value.toFixed(1) + "%", }, ...CarSetupParams.map(param => { const idx = param.index; return { field: 'arr_' + idx, headerName: param.name, valueGetter: ({ row }) => row.arr ? row.arr[idx] : 0, renderCell: ({ row }) => { const value = row.arr ? row.arr[idx] : 0; const carSetupDiff = value - (prevCarSetup ? prevCarSetup[idx] : 0); return 0 ? "#ff6383" : carSetupDiff < 0 ? "#76ff03" : "white" }} > {carSetupDiff > 0 ? "▲" : carSetupDiff < 0 ? "▼" : ""} { param.render(value * (param.max - param.min) + param.min) } }, } }), { field: 'arr', headerName: '⇒', width: 8, renderCell: () => "⇒", }, ...BiasParams.map(param => { const idx = param.index; return { field: 'biasArr_' + idx, headerName: param.name, valueGetter: ({ row }) => row.biasParams ? row.biasParams[idx] : 0, renderCell: ({ row }) => { const value = row.biasParams ? row.biasParams[idx] : 0; const carSetupDiff = value - (prevBiasParam ? prevBiasParam[idx] : 0); return 0 ? "#ff6383" : carSetupDiff < 0 ? "#76ff03" : "white" }} > {value.toFixed(4)} {carSetupDiff > 0 ? "▲" : carSetupDiff < 0 ? "▼" : ""} }, } }) ]} density="compact" rowsPerPageOptions={[10, 20, 40, 100, 200]} />
Previous Runs FWA RWA ARD TC TO Oversteer Braking Cornering Traction Straights { previousRuns.map(x => { return ( {trackMap[x.track]?.name} { [0, 1, 2, 3, 4].map(idx => ( { CarSetupParams[idx].render( x.carSetup[idx] * ( CarSetupParams[idx].max - CarSetupParams[idx].min ) + CarSetupParams[idx].min ) } )) } { [0, 1, 2, 3, 4].map(idx => ( { x["feedback_" + idx] && ( ) } )) } ) }) }
) } catch (e) { console.log(e); // delete localStorage[target]; // document.location.reload(); } } export default dynamic(() => Promise.resolve(Calculator), { ssr: false, });