This commit is contained in:
Tommy Parnell
2024-05-10 00:49:29 -04:00
parent 0ea6df0a49
commit 4ad624b369
6 changed files with 246 additions and 184 deletions

View File

@@ -3,11 +3,9 @@
</script> </script>
<body> <body>
<main class="container-fluid"> <main class="container-fluid">
<slot /> <slot />
</main> </main>
</body> </body>
<style> <style>

View File

@@ -9,7 +9,9 @@
$storedGames[$storedGames.length - 1]; $storedGames[$storedGames.length - 1];
$: currentScoreEditing = scoreEditing > -1 ? currentGameEditing.scores[scoreEditing] : null; $: currentScoreEditing = scoreEditing > -1 ? currentGameEditing.scores[scoreEditing] : null;
$: gamesComplete = $storedGames.filter((g) => g.isComplete); $: gamesComplete = $storedGames.filter((g) => g.isComplete);
$: completedStats = calculateTotalGameStats(gamesComplete);
$: disableEditing = currentGameEditing.isComplete && scoreEditing === -1; $: disableEditing = currentGameEditing.isComplete && scoreEditing === -1;
$: storedGames;
function isKill(score: Score | undefined | null) { function isKill(score: Score | undefined | null) {
return score === 'killHit6' || score === 'killHit8' || score === 'killMiss'; return score === 'killHit6' || score === 'killHit8' || score === 'killMiss';
} }
@@ -92,6 +94,39 @@
return current.stats.totalEightKills + current.stats.totalSixKills + accum; return current.stats.totalEightKills + current.stats.totalSixKills + accum;
}, 0); }, 0);
} }
// function calculateBullsHit(games: Game[]) {
// return games.reduce((accum: number, current) => {
// return current.stats.bulls + accum;
// }, 0);
// }
// function calculateTotalScore(games: Game[]) {
// return games.reduce((accum: number, current) => {
// return current.totalScore + accum;
// }, 0);
// }
function calculateTotalGameStats(gamesToCalc: Game[]) {
const stats = gamesToCalc.reduce(
(accum, current) => {
return {
bulls: current.stats.bulls + accum.bulls,
kills: current.stats.totalEightKills + current.stats.totalSixKills + accum.kills,
drops: current.stats.drops + accum.drops,
totalScore: current.totalScore + accum.totalScore
};
},
{ bulls: 0, kills: 0, drops: 0, totalScore: 0 } as {
bulls: number;
kills: number;
drops: number;
totalScore: number;
}
);
return {
...stats,
average: stats.totalScore && gamesToCalc.length ? stats.totalScore / gamesToCalc.length : 0,
rating: calculateRating(gamesToCalc)
};
}
function getLabelForScore(score: Score) { function getLabelForScore(score: Score) {
if (typeof score === 'number') { if (typeof score === 'number') {
return score.toString(); return score.toString();
@@ -118,19 +153,24 @@
</svelte:head> </svelte:head>
<h1 class="center">WATL rating simulator</h1> <h1 class="center">WATL rating simulator</h1>
<section> <section>
<div class="stats"><div>Score {currentGameEditing.totalScore}</div> <div>Throw: {currentGameEditing.scores.length}</div></div> <div class="stats">
<div>Score {currentGameEditing.totalScore}</div>
<div>Throw: {currentGameEditing.scores.length + 1}</div>
</div>
<div class="flexrow"> <div class="flexrow">
{#each currentGameEditing.scores as score, scoreIndex} {#each currentGameEditing.scores as score, scoreIndex}
<div> <div>
{#if scoreIndex === scoreEditing} {#if scoreIndex === scoreEditing}
<button <button
class="outline" class="outline"
on:click={() => { on:click={() => {
scoreEditing = -1; scoreEditing = -1;
}}>Cancel</button }}>Cancel</button
> >
{:else} {:else}
<button class="outline secondary" on:click={() => (scoreEditing = scoreIndex)}>{getLabelForScore(score)}</button> <button class="outline secondary" on:click={() => (scoreEditing = scoreIndex)}
>{getLabelForScore(score)}</button
>
{/if} {/if}
</div> </div>
{/each} {/each}
@@ -139,58 +179,58 @@
<section> <section>
<div> <div>
<button <button
class="flexrowButton " class="flexrowButton"
disabled={disableEditing} disabled={disableEditing}
on:click={() => { on:click={() => {
setScore(0); setScore(0);
}}>0</button }}>0</button
> >
<button <button
class="flexrowButton" class="flexrowButton"
disabled={disableEditing} disabled={disableEditing}
on:click={() => { on:click={() => {
setScore(1); setScore(1);
}}>1</button }}>1</button
> >
<button <button
class="flexrowButton" class="flexrowButton"
disabled={disableEditing} disabled={disableEditing}
on:click={() => { on:click={() => {
setScore(2); setScore(2);
}}>2</button }}>2</button
> >
<button <button
class="flexrowButton " class="flexrowButton"
disabled={disableEditing} disabled={disableEditing}
on:click={() => { on:click={() => {
setScore(3); setScore(3);
}}>3</button }}>3</button
> >
<button <button
class="flexrowButton " class="flexrowButton"
disabled={disableEditing} disabled={disableEditing}
on:click={() => { on:click={() => {
setScore(4); setScore(4);
}}>4</button }}>4</button
> >
<button <button
class="flexrowButton " class="flexrowButton"
disabled={disableEditing} disabled={disableEditing}
on:click={() => { on:click={() => {
setScore(5); setScore(5);
}}>5</button }}>5</button
> >
<button <button
class="flexrowButton " class="flexrowButton"
disabled={disableEditing} disabled={disableEditing}
on:click={() => { on:click={() => {
setScore(6); setScore(6);
}}>6</button }}>6</button
> >
</div> </div>
<div> <div>
<button <button
class="flexrowButton " class="flexrowButton"
disabled={disableEditing || disabled={disableEditing ||
(!currentGameEditing.KillEnabled && (!currentGameEditing.KillEnabled &&
!isKill(currentScoreEditing) && !isKill(currentScoreEditing) &&
@@ -200,7 +240,7 @@
}}>{getLabelForScore('killHit6')}</button }}>{getLabelForScore('killHit6')}</button
> >
<button <button
class="flexrowButton " class="flexrowButton"
disabled={disableEditing || disabled={disableEditing ||
(!currentGameEditing.KillEnabled && (!currentGameEditing.KillEnabled &&
!isKill(currentScoreEditing) && !isKill(currentScoreEditing) &&
@@ -210,7 +250,7 @@
}}>{getLabelForScore('killHit8')}</button }}>{getLabelForScore('killHit8')}</button
> >
<button <button
class="flexrowButton " class="flexrowButton"
disabled={disableEditing || disabled={disableEditing ||
(!currentGameEditing.KillEnabled && (!currentGameEditing.KillEnabled &&
!isKill(currentScoreEditing) && !isKill(currentScoreEditing) &&
@@ -220,14 +260,16 @@
}}>{getLabelForScore('killMiss')}</button }}>{getLabelForScore('killMiss')}</button
> >
<button <button
class="flexrowButton " class="flexrowButton"
disabled={disableEditing} disabled={disableEditing}
on:click={() => { on:click={() => {
setScore('drop'); setScore('drop');
}}>{getLabelForScore('drop')}</button }}>{getLabelForScore('drop')}</button
> >
<!-- {#if currentGameEditing.isComplete} --> <!-- {#if currentGameEditing.isComplete} -->
<button disabled={!currentGameEditing.isComplete} class="contrast" on:click={saveGame}>Save Game</button> <button disabled={!currentGameEditing.isComplete} class="contrast" on:click={saveGame}
>New Game</button
>
<!-- {/if} --> <!-- {/if} -->
</div> </div>
</section> </section>
@@ -236,11 +278,14 @@
<h2>Past Games</h2> <h2>Past Games</h2>
<button on:click={deleteAllGames}>Delete All</button><br /><br /> <button on:click={deleteAllGames}>Delete All</button><br /><br />
<p> <p>
Rating: {calculateRating(gamesComplete)} <br /> Average: {calculateAverage(gamesComplete)} <br /> total kills: Rating: {completedStats.rating} <br />
{calculateKillsHit(gamesComplete)} Average: {completedStats.average} <br />
Kills: {completedStats.kills} <br />
Bulls: {completedStats.bulls} <br />
Score: {completedStats.totalScore}
</p> </p>
<div class="overflow-auto"> <div class="overflow-auto">
<table class="striped"> <table class="">
<thead> <thead>
<tr> <tr>
<th>Game</th> <th>Game</th>
@@ -255,35 +300,39 @@
</thead> </thead>
<tbody> <tbody>
{#each $storedGames as game, gameIndex} {#each $storedGames as game, gameIndex}
<tr> {#if game.isComplete || (!game.isComplete && currentGameEditing !== game)}
<td class="tdSmall">{gameIndex + 1}</td> <tr>
<td class="tdSmall">{game.totalScore}</td> <td class="tdSmall">{gameIndex + 1}</td>
<td class="tdSmall">{game.stats.bulls}</td> <td class="tdSmall">{game.totalScore}</td>
<td class="tdSmall">{game.stats.totalEightKills}</td> <td class="tdSmall">{game.stats.bulls}</td>
<td class="tdSmall">{game.stats.totalSixKills}</td> <td class="tdSmall">{game.stats.totalEightKills}</td>
<td class="tdSmall">{game.stats.drops}</td> <td class="tdSmall">{game.stats.totalSixKills}</td>
<td <td class="tdSmall">{game.stats.drops}</td>
><button <td
disabled={game === currentGameEditing} ><button
on:click={() => { disabled={game === currentGameEditing}
gameEditing = game.gameId; on:click={() => {
}}>Edit</button gameEditing = game.gameId;
> }}>Edit</button
</td> >
<td> </td>
<button <td>
disabled={game === currentGameEditing} <button
on:click={() => { disabled={game === currentGameEditing}
const newGames = ($storedGames = $storedGames.filter((g, i) => i !== gameIndex)); on:click={() => {
if (newGames.length < 1) { const newGames = ($storedGames = $storedGames.filter(
newGames.push(new Game()); (g, i) => i !== gameIndex
} ));
$storedGames = newGames; if (newGames.length < 1) {
gameEditing = ''; newGames.push(new Game());
}}>Delete</button }
> $storedGames = newGames;
</td> gameEditing = '';
</tr> }}>Delete</button
>
</td>
</tr>
{/if}
{/each} {/each}
</tbody> </tbody>
</table> </table>
@@ -294,7 +343,6 @@
.stats { .stats {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.center { .center {
display: flex; display: flex;
@@ -307,7 +355,7 @@
min-height: 7rem; min-height: 7rem;
flex-wrap: wrap; flex-wrap: wrap;
} }
.flexrowButton { .flexrowButton {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
/* mobile only */ /* mobile only */

View File

@@ -1,33 +1,39 @@
export type Score = 8 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | "drop" | "killHit8" | "killHit6" | "killMiss"; export type Score = 8 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 'drop' | 'killHit8' | 'killHit6' | 'killMiss';
export interface GameStats { totalSixKills: number; totalEightKills: number; killAttempts: number, drops: number, bulls: number} export interface GameStats {
totalSixKills: number;
totalEightKills: number;
killAttempts: number;
drops: number;
bulls: number;
}
export interface IStoredGame { export interface IStoredGame {
scores: Score[]; scores: Score[];
stats: GameStats; stats: GameStats;
gameId: string; gameId: string;
} }
export class Game implements IStoredGame { export class Game implements IStoredGame {
public gameId = Math.random().toString(36).substring(7); public gameId = Math.random().toString(36).substring(7);
public scores: Score[] = []; public scores: Score[] = [];
public stats: GameStats = this.GenerateStats(); public stats: GameStats = this.GenerateStats();
public addScore(score: Score) { public addScore(score: Score) {
this.scores.push(score); this.scores.push(score);
this.scores = [...this.scores] this.scores = [...this.scores];
this.stats = this.GenerateStats(); this.stats = this.GenerateStats();
} }
public removeScore(scoreNumber: number) { public removeScore(scoreNumber: number) {
this.scores.splice(scoreNumber, 1); this.scores.splice(scoreNumber, 1);
this.scores = [...this.scores] this.scores = [...this.scores];
this.stats = this.GenerateStats(); this.stats = this.GenerateStats();
} }
public replaceScore(scoreNumber: number, score: Score) { public replaceScore(scoreNumber: number, score: Score) {
this.scores[scoreNumber] = score; this.scores[scoreNumber] = score;
this.scores = [...this.scores] this.scores = [...this.scores];
this.stats = this.GenerateStats(); this.stats = this.GenerateStats();
} }
public get isComplete() { public get isComplete() {
return this.scores.length === 10; return this.scores.length === 10;
} }
public convertScoreToNumber(score: Score): number { public convertScoreToNumber(score: Score): number {
if (score === 'killHit6') { if (score === 'killHit6') {
return 6; return 6;
} }
@@ -37,46 +43,51 @@ export class Game implements IStoredGame {
if (score === 'killMiss') { if (score === 'killMiss') {
return 0; return 0;
} }
if (score === 'drop') { if (score === 'drop') {
return 0; return 0;
} }
return score; return score;
} }
public get totalScore() : number { public get totalScore(): number {
return this.scores.reduce((acc: number, current) => { return this.scores.reduce((acc: number, current) => {
return acc + this.convertScoreToNumber(current); return acc + this.convertScoreToNumber(current);
}, 0); }, 0);
} }
public get KillEnabled() { public get KillEnabled() {
const stats = this.GenerateStats(); const stats = this.GenerateStats();
if(stats.killAttempts < 2) { if (stats.killAttempts < 2) {
return true; return true;
} }
if(stats.killAttempts === 2 && stats.drops > 0) { if (stats.killAttempts === 2 && stats.drops > 0) {
return true; return true;
} }
return false; return false;
} }
public GenerateStats(): { totalSixKills: number; totalEightKills: number; killAttempts: number, drops: number, bulls: number} { public GenerateStats(): {
const data = { totalSixKills: 0, totalEightKills: 0, killAttempts: 0, drops: 0, bulls: 0} totalSixKills: number;
this.scores.forEach(score => { totalEightKills: number;
if(score === 6) { killAttempts: number;
data.bulls += 1 drops: number;
} bulls: number;
if(score === "drop") { } {
data.drops += 1; const data = { totalSixKills: 0, totalEightKills: 0, killAttempts: 0, drops: 0, bulls: 0 };
} this.scores.forEach((score) => {
if( score === "killHit6" || score === "killHit8" || score === "killMiss") { if (score === 6) {
data.killAttempts += 1; data.bulls += 1;
} }
if(score === "killHit6") { if (score === 'drop') {
data.totalSixKills += 1; data.drops += 1;
} }
if(score === "killHit8") { if (score === 'killHit6' || score === 'killHit8' || score === 'killMiss') {
data.totalEightKills += 1; data.killAttempts += 1;
} }
if (score === 'killHit6') {
}) data.totalSixKills += 1;
return data; }
} if (score === 'killHit8') {
data.totalEightKills += 1;
}
});
return data;
}
} }

View File

@@ -1,30 +1,35 @@
import { writable } from 'svelte/store';
import { writable } from "svelte/store"; import { Game, type IStoredGame } from './interfaces';
import { Game, type IStoredGame } from "./interfaces"; import { browser } from '$app/environment';
import { browser } from "$app/environment"; const DATASTORAGEKEY = 'WATLCALC';
const DATASTORAGEKEY = "WATLCALC";
const gamesFromLocalStorage = browser ? localStorage.getItem(DATASTORAGEKEY) : null; const gamesFromLocalStorage = browser ? localStorage.getItem(DATASTORAGEKEY) : null;
const parsedGamesFromLocalStorage: IStoredGame[] | undefined | null = gamesFromLocalStorage ? JSON.parse(gamesFromLocalStorage) : null; const parsedGamesFromLocalStorage: IStoredGame[] | undefined | null = gamesFromLocalStorage
? JSON.parse(gamesFromLocalStorage)
: null;
let gamesParsed: Game[] = []; let gamesParsed: Game[] = [];
if(!parsedGamesFromLocalStorage || parsedGamesFromLocalStorage?.length === 0) { if (!parsedGamesFromLocalStorage || parsedGamesFromLocalStorage?.length === 0) {
gamesParsed = [new Game()]; gamesParsed = [new Game()];
} }
if(parsedGamesFromLocalStorage && Array.isArray(parsedGamesFromLocalStorage) && parsedGamesFromLocalStorage.length > 0) { if (
gamesParsed = parsedGamesFromLocalStorage.map(i => deserializeGames(i)); parsedGamesFromLocalStorage &&
Array.isArray(parsedGamesFromLocalStorage) &&
parsedGamesFromLocalStorage.length > 0
) {
gamesParsed = parsedGamesFromLocalStorage.map((i) => deserializeGames(i));
} }
export const storedGames = writable<Game[]>(gamesParsed); export const storedGames = writable<Game[]>(gamesParsed);
storedGames.subscribe(value => { storedGames.subscribe((value) => {
browser && localStorage.setItem(DATASTORAGEKEY, JSON.stringify(value)); browser && localStorage.setItem(DATASTORAGEKEY, JSON.stringify(value));
}); });
function deserializeGames(loadedDate: IStoredGame | Game): Game { function deserializeGames(loadedDate: IStoredGame | Game): Game {
if(loadedDate instanceof Game) { if (loadedDate instanceof Game) {
return loadedDate; return loadedDate;
} }
const game = new Game(); const game = new Game();
game.scores = loadedDate.scores; game.scores = loadedDate.scores;
game.stats = loadedDate.stats; game.stats = loadedDate.stats;
game.gameId = loadedDate.gameId; game.gameId = loadedDate.gameId;
return game; return game;
} }

View File

@@ -12,10 +12,10 @@ const config = {
// If your environment is not supported, or you settled on a specific environment, switch out the adapter. // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters. // See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({ adapter: adapter({
fallback: 'index.html', fallback: 'index.html'
}), }),
paths: { paths: {
base: process.env.NODE_ENV === 'production' ? '/WATLRatingCalculator' : '', base: process.env.NODE_ENV === 'production' ? '/WATLRatingCalculator' : ''
} }
} }
}; };