diff --git a/src/lib/ActionInputStateMachine.svelte.ts b/src/lib/ActionInputStateMachine.svelte.ts index 2a47bd0..280b5f7 100644 --- a/src/lib/ActionInputStateMachine.svelte.ts +++ b/src/lib/ActionInputStateMachine.svelte.ts @@ -19,6 +19,10 @@ export class ActionInputVerifier { }; } + public actions_are_ok(action_data: AutoActionData[]): boolean { + return action_data.every((action) => action.ok); + } + public verify_actions(action_data: AutoActionData[]) { action_data.forEach((action) => { action.ok = this.verify_new_action(action); @@ -32,19 +36,20 @@ export class ActionInputVerifier { verify_new_action(action_data: AutoActionData): boolean { const success = action_data.success; const action = action_data.action; - console.log(action); if (action.includes('InternalTote') && this.held_totes === 0) return false; if (success) { if (action.includes('IntakeBalloon')) this.held_balloons++; else if (action.includes('IntakeBunny')) this.held_bunnies++; else if (action.includes('IntakeTote')) this.held_totes++; + else if (action.includes('PreloadBunny')) this.held_bunnies++; + else if (action.includes('PreloadBalloon')) this.held_balloons++; else if (action.includes('EjectBalloon')) this.held_balloons--; else if (action.includes('EjectBunny')) this.held_bunnies--; else if (action.includes('EjectTote')) this.held_totes--; } else { - if (action.includes('EjectBalloon') && this.held_balloons <= 0) return false; - else if (action.includes('EjectBunny') && this.held_bunnies <= 0) return false; - else if (action.includes('EjectTote') && this.held_totes <= 0) return false; + if (action.includes('EjectBunny') && this.held_bunnies === 0) return false; + else if (action.includes('EjectBalloon') && this.held_balloons === 0) return false; + else if (action.includes('EjectTote') && this.held_totes === 0) return false; } if (action.includes('ScoreBalloon')) this.held_balloons--; else if (action.includes('ScoreBunny')) this.held_bunnies--; diff --git a/src/lib/localStore.svelte.ts b/src/lib/localStore.svelte.ts new file mode 100644 index 0000000..3d82a89 --- /dev/null +++ b/src/lib/localStore.svelte.ts @@ -0,0 +1,31 @@ +import { browser } from '$app/environment'; + +export class LocalStore { + value = $state() as T[]; + key = ''; + + constructor(key: string, value: T[]) { + this.value = value; + this.key = key; + + if (browser) { + const item = localStorage.getItem(key); + if (item) this.value = this.deserialize(item); + } + + $effect(() => { + if (this.value.length > 12) { + this.value.splice(0, 1); + } + localStorage.setItem(this.key, this.serialize(this.value)); + }); + } + + serialize(value: T[]): string { + return JSON.stringify(value); + } + + deserialize(item: string): T[] { + return JSON.parse(item); + } +} diff --git a/src/lib/server-assets/database.ts b/src/lib/server-assets/database.ts index 6f5e587..093d4f0 100644 --- a/src/lib/server-assets/database.ts +++ b/src/lib/server-assets/database.ts @@ -24,8 +24,6 @@ const event_key = 'orbb2024'; // Whether or not the database is currently being used const use_db: boolean = USE_DB === 'true'; -console.log(Number.parseInt(DB_PORT)); - const db = new Client({ user: DB_USER, password: DB_PASSWORD, @@ -234,6 +232,19 @@ export async function insertUser(name: string): Promise { } } +export async function delete_team_match( + match_key: string, + team_key: string +): Promise { + if (!use_db) return null; + + const response = await db.query( + 'DELETE FROM "TeamMatches" WHERE "match_key" = $1 AND "team_key" = $2', + [match_key, team_key] + ); + return response.rowCount; +} + export async function select(matchkey: string, teamkey: string) { if (!use_db) return null; diff --git a/src/lib/types.ts b/src/lib/types.ts index 8b4cff0..8fa066b 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -97,7 +97,9 @@ export type BunnyAction = | 'ScoreBunnyExternalTote' | 'ScoreBunnyUncontrolledTote' | 'ScoreBunnyLow'; -export type AutoAction = TeleAction | BunnyAction; + +export type PreAction = 'PreloadBunny' | 'PreloadBalloon'; +export type AutoAction = TeleAction | BunnyAction | PreAction; export type TeleHeldItems = { balloons: number; diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte index a866ff4..d831396 100644 --- a/src/routes/admin/+page.svelte +++ b/src/routes/admin/+page.svelte @@ -1,15 +1,31 @@ @@ -120,8 +203,8 @@
-

Red

-

Blue

+

Red

+

Blue

{#each teams as _, i} @@ -130,44 +213,176 @@
- + -
-

Queued Scouts

-
+
+

Queued Scouts

+
{#each scout_queue as scout} - {/each}
-
-
-

Queued Robots

- +
+
+

Queued Robots

+
-
- {#each robot_queue as robot} - {/each}
-
-

Team-Match Submissions

-
- {#each submitted_team_matches as team_match} +
+
+

Team-Match Submissions

+ +
+
+ {#each team_matches.value as team_match} +
{/each}
+ + {#snippet header()} +
+
+

{displayed_team_match.team_match_key}

+
+ +
+ {/snippet} +
+ {#if displayed_team_match.timeline !== null} +
+

Auto

+
+ {#each displayed_team_match.timeline?.auto_actions as auto_action} +
+
+
+ {auto_action.action} +
+
+ {/each} +
+
+
+

Tele

+
+ {#each displayed_team_match.timeline?.tele_actions as tele_action} +
+
+
+ {tele_action.action} +
+
+ {/each} +
+
+ {/if} +
+ +
+ + + {#snippet header()} +

Are You Sure?

+ {/snippet} +
+ + +
+
diff --git a/src/routes/admin/Modal.svelte b/src/routes/admin/Modal.svelte new file mode 100644 index 0000000..4b756a8 --- /dev/null +++ b/src/routes/admin/Modal.svelte @@ -0,0 +1,64 @@ + + + + (show_modal = false)} + onclick={(e) => { + if (e.target === dialog) dialog!.close(); + }} +> +
+ {@render header?.()} + {@render children?.()} +
+
+ + diff --git a/src/routes/api/delete/[match_key]/[team_key]/+server.ts b/src/routes/api/delete/[match_key]/[team_key]/+server.ts new file mode 100644 index 0000000..ed975cc --- /dev/null +++ b/src/routes/api/delete/[match_key]/[team_key]/+server.ts @@ -0,0 +1,7 @@ +import type { RequestHandler } from './$types'; +import { delete_team_match } from '$lib/server-assets/database'; +import { json } from '@sveltejs/kit'; + +export const DELETE: RequestHandler = async ({ params }: any) => { + return json(((await delete_team_match(params.team_match, params.team_key)) ?? 0) > 0); +}; diff --git a/src/routes/api/submit/+server.ts b/src/routes/api/submit/+server.ts index 0cc0cf3..252b1d8 100644 --- a/src/routes/api/submit/+server.ts +++ b/src/routes/api/submit/+server.ts @@ -5,5 +5,5 @@ import { insertTeamMatch, insertUser } from '$lib/server-assets/database'; export const POST: RequestHandler = async ({ request }) => { const match: TeamMatch = await request.json(); - return json((await insertTeamMatch(match)) && (await insertUser(match.scout_id))); + return json({ ok: (await insertTeamMatch(match)) && (await insertUser(match.scout_id)) }); }; diff --git a/src/routes/queue/+page.svelte b/src/routes/queue/+page.svelte index 5ef2630..2f78b3a 100644 --- a/src/routes/queue/+page.svelte +++ b/src/routes/queue/+page.svelte @@ -2,10 +2,9 @@ import { browser } from '$app/environment'; import { goto } from '$app/navigation'; import { io, Socket } from 'socket.io-client'; - const username = 'test_scout'; - let socket: Socket; - socket = io({ + const username = browser && localStorage.getItem('username'); + let socket: Socket = io({ auth: { username } @@ -26,7 +25,7 @@ }); const leave = () => { - socket.emit('leave_scout_queue', 'test_scout'); + socket.emit('leave_scout_queue', username); goto('/'); }; diff --git a/src/routes/scout/[team_data]/+page.svelte b/src/routes/scout/[team_data]/+page.svelte index 418c65f..4b41a10 100644 --- a/src/routes/scout/[team_data]/+page.svelte +++ b/src/routes/scout/[team_data]/+page.svelte @@ -9,6 +9,7 @@ import Postmatch from './Postmatch.svelte'; import { io } from 'socket.io-client'; import { goto } from '$app/navigation'; + import { ActionInputVerifier } from '$lib/ActionInputStateMachine.svelte'; const { data }: { data: PageData } = $props(); @@ -20,8 +21,10 @@ balloons: 0, totes: 0 }); - // The furthest index in actions that was made during auto + // The furthest index in actions that was made during auto or pre + let furthest_pre_index = $state(0); let furthest_auto_index = $state(0); + let preload_bunny = $state(false); let quickness = $state(3); let awareness = $state(3); @@ -30,14 +33,28 @@ let notes = $state(''); let timeline_extended = $state(false); - let gamePhase = $state('Auto') as 'Auto' | 'Tele' | 'Post'; - let pageName = $state(''); + let gamePhase = $state('Pre') as 'Pre' | 'Auto' | 'Tele' | 'Post'; + let pageName = $state('Home'); function phaseShiftRight() { - gamePhase = gamePhase === 'Auto' ? 'Tele' : gamePhase === 'Tele' ? 'Post' : 'Post'; // Last case should never happen + gamePhase = + gamePhase === 'Pre' + ? 'Auto' + : gamePhase === 'Auto' + ? 'Tele' + : gamePhase === 'Tele' + ? 'Post' + : 'Post'; // Last case should never happen } function phaseShiftLeft() { - gamePhase = gamePhase === 'Post' ? 'Tele' : gamePhase === 'Tele' ? 'Auto' : 'Auto'; // Last case should never happen + gamePhase = + gamePhase === 'Post' + ? 'Tele' + : gamePhase === 'Tele' + ? 'Auto' + : gamePhase === 'Auto' + ? 'Pre' + : 'Pre'; // Last case should never happen } const socket = io({ @@ -47,8 +64,14 @@ }); async function submit() { - const auto_actions = actions.slice(0, furthest_auto_index + 1); - const tele_actions = actions.slice(furthest_auto_index + 1) as TeleActionData[]; // TODO: Add verification function to ensure that this always works + const _preload_actions = actions.slice(0, furthest_pre_index); + const auto_actions = actions.slice(furthest_pre_index, furthest_auto_index); + const tele_actions = actions.slice(furthest_auto_index) as TeleActionData[]; + + // console.log(`Preload Actions: ${preload_actions.map((action) => action.action)}`) + // console.log(`Auto Actions: ${auto_actions.map((action) => action.action)}`) + // console.log(`Tele Actions: ${tele_actions.map((action) => action.action)}`) + const match: TeamMatch = { id: 0, scout_id: username, @@ -73,7 +96,7 @@ if (!response.ok) { console.log('Failed to submit match'); - socket.emit('failed_submit_team_match', [match, response]); + socket.emit('failed_submit_team_match', match); } else { console.log(response); socket.emit('submit_team_match', match); @@ -81,15 +104,68 @@ goto('/'); } + + const increase_pre_balloon = () => { + actions.splice(furthest_pre_index, 0, { + action: 'PreloadBalloon', + success: true, + ok: true + }); + + furthest_pre_index++; + furthest_auto_index++; + held.balloons++; + }; + const decrease_pre_balloon = () => { + const verifier = new ActionInputVerifier(); + actions.splice(furthest_pre_index - 1, 1); + + furthest_pre_index--; + furthest_auto_index--; + held.balloons--; + verifier.verify_actions(actions); + }; + const toggle_pre_bunny = () => { + let verifier = new ActionInputVerifier(); + let pre_actions = actions.slice(0, furthest_pre_index); + const index = pre_actions.findIndex((action) => action.action === 'PreloadBunny'); + if (index === -1) { + actions.splice(furthest_pre_index, 1, { + action: 'PreloadBunny', + success: true, + ok: true + }); + furthest_pre_index++; + furthest_auto_index++; + held.bunnies++; + preload_bunny = true; + return; + } + actions.splice(index, 1); + furthest_pre_index--; + furthest_auto_index--; + held.bunnies--; + preload_bunny = false; + verifier.verify_actions(actions); + if (!verifier.actions_are_ok(actions)) { + timeline_extended = true; + } + };
-

Team {data.team_key}

+

+ Team {data.team_key} +

{gamePhase}: {pageName}

@@ -101,7 +177,58 @@
- {#if gamePhase === 'Auto'} + {#if gamePhase === 'Pre'} +
+
+ You're Scouting: +

+ {data.team_key} +

+
+ + Preload Balloon: {held.balloons} + +
+ + +
+ + +
+ {:else if gamePhase === 'Auto'} - {#if held_scorables > 0} - - {/if} - {#if held_ejectables > 0} - - {/if} + +
{:else if is_intake_state}
(actionState = 'IntakeBalloon')}>Balloon: Coral
- - {#if held.totes > 0} - - {/if} - -
+
+

Bunny

+
+ + + +
- {/if} - {#if held.balloons > 0} -
-

Ballon

-
- - - {#if held.totes > 0} - - {/if} - -
+
+
+

Balloon

+
+ + + +
- {/if} +
{:else if is_eject_state}
- {#if held.bunnies > 0} - - {/if} - {#if held.balloons > 0} - - {/if} - {#if held.totes > 0} - - {/if} + + +
-
-
-

Post Match

+
+
+

Post Match

-
-
-
-

Robot

+
+

Condition

-