diff --git a/src/components/SpoilsCalculator/SpoilsCalculator.tsx b/src/components/SpoilsCalculator/SpoilsCalculator.tsx index cd7a2931..c2d2e50b 100644 --- a/src/components/SpoilsCalculator/SpoilsCalculator.tsx +++ b/src/components/SpoilsCalculator/SpoilsCalculator.tsx @@ -1,86 +1,182 @@ -import { FunctionComponent, useState } from 'react'; -import { Box, Button, TextField } from '@mui/material'; +import { ChangeEvent, FunctionComponent, useEffect, useState } from 'react'; +import { + Box, + Button, + Divider, + TextField, + Unstable_Grid2 as Grid, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from '@mui/material'; + +interface Input { + copies?: number; + deck?: number; + life?: number; + samples?: number; +} + +interface Result { + average: string; + deathes: string; + id: number; + input: Input; +} + +const INITIAL_INPUT: Input = { copies: 4, deck: 53, life: 20, samples: 10000 }; export const SpoilsCalculator: FunctionComponent = () => { - const [deckSize, setDeckSize] = useState(0); - const [cardsLeft, setCardsLeft] = useState(0); - const [lifeTotal, setLifeTotal] = useState(0); - const [avgLifeLoss, setAvgLifeLoss] = useState(0); - const [deathPct, setDeathPct] = useState(0); + const [input, setInput] = useState(INITIAL_INPUT); + const [isDisabled, setIsDisabled] = useState(true); + const [results, setResults] = useState([]); - /** In-place Durstenfeld shuffle. */ - const shuffle = (array: number[]): void => { - for (let i = array.length - 1; i > 0; i -= 1) { - const j = Math.floor(Math.random() * (i + 1)); - const temp = array[i]; - array[i] = array[j]; - array[j] = temp; + useEffect(() => { + setIsDisabled(!Object.values(input).every(Boolean)); + }, [input]); + + const onChange = + (key: keyof Input) => + ({ target }: ChangeEvent) => + setInput((previous) => ({ + ...previous, + [key]: parseInt(target.value, 10) || undefined, + })); + + /** + * Return a randomized array of numbers corresponding to a randomized deck. + * Implements a simple Durstenfeld shuffle. + */ + const shuffle = (size: number): number[] => { + const deck = [...Array(size).keys()]; + for (let left = deck.length - 1; left > 0; left -= 1) { + const right = Math.floor(Math.random() * (left + 1)); + [deck[left], deck[right]] = [deck[right] as number, deck[left] as number]; } + return deck; }; - const calculate = (): void => { - const numIters = 10000; - let avg = 0.0; - let deathCounter = 0.0; - for (let i = 0; i < numIters; i += 1) { - let cardsSeen = 0; - let deathFlag = false; - const deck = [...Array(deckSize).keys()]; - shuffle(deck); - while (cardsSeen < deckSize) { - cardsSeen += 1; - if (cardsSeen > lifeTotal) { - deathFlag = true; + const onCompute = (): void => { + if (input.copies && input.deck && input.life && input.samples) { + setIsDisabled(true); + let average = 0; + let deathes = 0; + for (let sample = 0; sample < input.samples; sample += 1) { + const deck = shuffle(input.deck); + let candidate = 0; + while (candidate < input.deck) { + if ((deck[candidate] as number) < input.copies) { + average += candidate; + break; + } + candidate += 1; } - if (deck[cardsSeen] < cardsLeft) { - avg += cardsSeen; - break; + if (candidate >= input.life) { + deathes += 1; } } - if (deathFlag) { - deathCounter += 1; - } + const result = { + average: (average / input.samples).toLocaleString(undefined, { + minimumFractionDigits: 1, + maximumFractionDigits: 1, + }), + deathes: (deathes / input.samples).toLocaleString(undefined, { + minimumFractionDigits: 2, + style: 'percent', + }), + input, + }; + setResults((previous) => [ + { ...result, id: previous.length }, + ...previous, + ]); + setIsDisabled(false); } - avg /= numIters; - setAvgLifeLoss(avg); - setDeathPct((deathCounter / numIters) * 100); }; return ( - setDeckSize(+t)} - /> - setCardsLeft(+t)} - /> - setLifeTotal(+t)} - /> - - - + {results.length ? ( + mixins.barf}> + + + + + Input + Average Life Loss + Death Likelihood + + + + {results.map(({ average, deathes, id, input }) => ( + + + {input.copies} out of {input.deck} with {input.life} life + + {average} + {deathes} + + ))} + +
+
+ ) : ( + + Input your board state and compute to see the success rate. + + )}
); };