From 323a62877c9b612ddba0d0dc21ea827d47e7471b Mon Sep 17 00:00:00 2001 From: Sukant Sathishkumar Date: Thu, 12 Mar 2026 21:03:47 -0400 Subject: [PATCH 1/2] added box and other tasks in SCRUM-9 --- app/match/auton.tsx | 188 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 156 insertions(+), 32 deletions(-) diff --git a/app/match/auton.tsx b/app/match/auton.tsx index 28a6bde..318eb2c 100644 --- a/app/match/auton.tsx +++ b/app/match/auton.tsx @@ -2,48 +2,172 @@ import { RootView, ThemedButton } from "@/components"; import { useRotateOnEnter } from "@/hooks/rotate-on-enter"; import { useRouter } from "expo-router"; import { OrientationLock } from "expo-screen-orientation"; -import { StyleSheet } from "react-native"; +import { useRef, useState } from 'react'; +import { StyleSheet, Text, View } from "react-native"; + +const Colors = { + background: "#191515", + backgroundFaint: "#262121", + border: "#443b3b", + text: "#eeecec", + highlight: "#ea2e2e", + highlightDark: "#ad1f1f", +}; export default function PreciseAuto() { useRotateOnEnter(OrientationLock.LANDSCAPE); const router = useRouter(); + + const [isTracking, setIsTracking] = useState(false); + const [timer, setTimer] = useState(17); + const [position, setPosition] = useState({ x: 50, y: 50 }); + const pathData = useRef([]); + const fieldRef = useRef(null); + const [fieldLayout, setFieldLayout] = useState({ x: 0, y: 0 }); + + const BOX_SIZE = 60; + + const startTracking = () => { + setIsTracking(true); + const dataInterval = setInterval(() => { + pathData.current.push({ + x: position.x.toFixed(1), + y: position.y.toFixed(1), + t: Date.now() + }); + }, 100); + + const countdownInterval = setInterval(() => { + setTimer((prev) => { + if (prev <= 1) { + clearInterval(countdownInterval); + return 0; + } + return prev - 1; + }); + }, 1000); + + setTimeout(() => { + clearInterval(dataInterval); + clearInterval(countdownInterval); + router.replace("/match/post-match"); + }, 17000); + }; + return ( - - { - router.replace("/(tabs)"); + + { + fieldRef.current?.measure((x, y, width, height, pageX, pageY) => { + setFieldLayout({ x: pageX, y: pageY }); + }); }} - /> - - { - router.replace("/match/post-match"); + style={styles.field} + onStartShouldSetResponder={() => true} + onResponderMove={(e) => { + const { pageX, pageY } = e.nativeEvent; + const relativeX = pageX - fieldLayout.x - BOX_SIZE / 2; + const relativeY = pageY - fieldLayout.y - BOX_SIZE / 2; + + const x = Math.max(0, Math.min(relativeX, 600 - BOX_SIZE)); + const y = Math.max(0, Math.min(relativeY, 300 - BOX_SIZE)); + + setPosition({ x, y }); }} - /> + > + + + {isTracking && ( + + + {timer}s + + )} + + + {!isTracking && ( + + router.replace("/(tabs)")} + /> + + + + )} ); } const styles = StyleSheet.create({ - smallButton: { - position: "absolute", - width: 80, - height: 32, + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: Colors.background + }, + field: { + width: 600, + height: 300, + backgroundColor: Colors.backgroundFaint, + borderWidth: 2, + borderColor: Colors.border, + borderRadius: 8, + position: 'relative', + overflow: 'hidden' + }, + timerOverlay: { + position: 'absolute', + top: 12, + right: 12, + backgroundColor: 'rgba(0,0,0,0.7)', + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 6, + borderWidth: 1, + borderColor: Colors.highlight + }, + recordingDot: { + width: 8, + height: 8, + borderRadius: 4, + backgroundColor: Colors.highlight, + marginRight: 8 + }, + timerText: { + color: Colors.text, + fontWeight: 'bold', + fontFamily: 'monospace', + fontSize: 14 + }, + buttonRow: { + flexDirection: 'row', + marginTop: 20, }, -}); + navButton: { + width: 140, + height: 44, + } +}); \ No newline at end of file From 8f7c5d29f979da5d5ba461d9a228f6072951505b Mon Sep 17 00:00:00 2001 From: Dalton Su Date: Sun, 15 Mar 2026 14:58:13 -0400 Subject: [PATCH 2/2] updated auton scouting to be roughly final version --- app/match/auton.tsx | 212 ++++++++++++++++-------------- components/index.ts | 2 + components/themed/arena-svg.tsx | 198 ++++++++++++++++++++++++++++ components/themed/themed-view.tsx | 2 + constants/theme.ts | 5 + 5 files changed, 320 insertions(+), 99 deletions(-) create mode 100644 components/themed/arena-svg.tsx diff --git a/app/match/auton.tsx b/app/match/auton.tsx index 318eb2c..4e41c2d 100644 --- a/app/match/auton.tsx +++ b/app/match/auton.tsx @@ -1,39 +1,48 @@ -import { RootView, ThemedButton } from "@/components"; +import { + ArenaSVG, + RootView, + ThemedButton, + ThemedText, + ThemedView, +} from "@/components"; +import { arenaH, arenaW } from "@/components/themed/arena-svg"; import { useRotateOnEnter } from "@/hooks/rotate-on-enter"; +import { useSettingsStore } from "@/hooks/settings-store"; import { useRouter } from "expo-router"; import { OrientationLock } from "expo-screen-orientation"; -import { useRef, useState } from 'react'; -import { StyleSheet, Text, View } from "react-native"; +import { useRef, useState } from "react"; +import { StyleSheet, View } from "react-native"; -const Colors = { - background: "#191515", - backgroundFaint: "#262121", - border: "#443b3b", - text: "#eeecec", - highlight: "#ea2e2e", - highlightDark: "#ad1f1f", -}; - -export default function PreciseAuto() { +export default function Auton() { useRotateOnEnter(OrientationLock.LANDSCAPE); const router = useRouter(); const [isTracking, setIsTracking] = useState(false); const [timer, setTimer] = useState(17); - const [position, setPosition] = useState({ x: 50, y: 50 }); - const pathData = useRef([]); + const startTime = useRef(0); + + const [position, setPosition] = useState({ posX: 210, posY: 0 }); + const pathData = useRef<{ posX: number; posY: number; time: number }[]>([]); + const fieldRef = useRef(null); - const [fieldLayout, setFieldLayout] = useState({ x: 0, y: 0 }); + const [fieldLayout, setFieldLayout] = useState({ + x: 0, + y: 0, + width: 0, + height: 0, + }); - const BOX_SIZE = 60; + const BOX_SIZE = (fieldLayout.height / 330) * 35; + + const fieldRotation = useSettingsStore((state) => state.fieldRotation); + const rot = fieldRotation === "br" ? -1 : 1; const startTracking = () => { setIsTracking(true); const dataInterval = setInterval(() => { pathData.current.push({ - x: position.x.toFixed(1), - y: position.y.toFixed(1), - t: Date.now() + ...position, + time: Date.now() - startTime.current, }); }, 100); @@ -56,118 +65,123 @@ export default function PreciseAuto() { return ( - + {isTracking ? ( + + + {timer}s + + ) : ( + <> + router.replace("/(tabs)")} + /> + + + + )} + + + { - fieldRef.current?.measure((x, y, width, height, pageX, pageY) => { - setFieldLayout({ x: pageX, y: pageY }); + fieldRef.current?.measure((_x, _y, width, height, pageX, pageY) => { + setFieldLayout({ x: pageX, y: pageY, width, height }); }); }} style={styles.field} onStartShouldSetResponder={() => true} onResponderMove={(e) => { const { pageX, pageY } = e.nativeEvent; - const relativeX = pageX - fieldLayout.x - BOX_SIZE / 2; - const relativeY = pageY - fieldLayout.y - BOX_SIZE / 2; + const relativeX = + ((pageX - fieldLayout.x) / fieldLayout.width) * 2 - 1; + const relativeY = + ((pageY - fieldLayout.y) / fieldLayout.height) * 2 - 1; + + const posX = + Math.max(-308, Math.min((relativeX * arenaW) / 2, 308)) * rot; + const posY = + Math.max(-141, Math.min((relativeY * arenaH) / 2, 141)) * rot; - const x = Math.max(0, Math.min(relativeX, 600 - BOX_SIZE)); - const y = Math.max(0, Math.min(relativeY, 300 - BOX_SIZE)); - - setPosition({ x, y }); + setPosition({ posX, posY }); }} > - + - - {isTracking && ( - - - {timer}s - - )} - - - {!isTracking && ( - - router.replace("/(tabs)")} - /> - - - - )} + ); } const styles = StyleSheet.create({ container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: Colors.background + paddingTop: 16, + alignItems: "center", + justifyContent: "center", }, field: { - width: 600, - height: 300, - backgroundColor: Colors.backgroundFaint, - borderWidth: 2, - borderColor: Colors.border, - borderRadius: 8, - position: 'relative', - overflow: 'hidden' + width: "100%", + flex: 1, + aspectRatio: arenaW / arenaH, + position: "relative", + overflow: "hidden", }, timerOverlay: { - position: 'absolute', - top: 12, - right: 12, - backgroundColor: 'rgba(0,0,0,0.7)', - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 12, - paddingVertical: 6, - borderRadius: 6, + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + borderRadius: 8, borderWidth: 1, - borderColor: Colors.highlight + height: 30, + width: 60, }, recordingDot: { width: 8, height: 8, borderRadius: 4, - backgroundColor: Colors.highlight, - marginRight: 8 - }, - timerText: { - color: Colors.text, - fontWeight: 'bold', - fontFamily: 'monospace', - fontSize: 14 + marginRight: 4, }, buttonRow: { - flexDirection: 'row', - marginTop: 20, + height: 30, + display: "flex", + flexDirection: "row", + justifyContent: "center", }, navButton: { - width: 140, - height: 44, - } -}); \ No newline at end of file + width: 70, + height: 30, + }, +}); diff --git a/components/index.ts b/components/index.ts index cf392f8..ab0f297 100644 --- a/components/index.ts +++ b/components/index.ts @@ -6,3 +6,5 @@ export { ThemedText } from "./themed/themed-text"; export { ThemedButton } from "./themed/themed-button"; export { ThemedTextInput } from "./themed/themed-text-input"; export { ThemedSelector } from "./themed/themed-selector"; + +export { ArenaSVG } from "./themed/arena-svg"; diff --git a/components/themed/arena-svg.tsx b/components/themed/arena-svg.tsx new file mode 100644 index 0000000..b0cbd22 --- /dev/null +++ b/components/themed/arena-svg.tsx @@ -0,0 +1,198 @@ +import { Colors } from "@/constants/theme"; +import { StyleProp, ViewStyle } from "react-native"; +import Svg, { G, Line, Polygon, Polyline, Rect } from "react-native-svg"; + +type ArenaSVGProps = { + style?: StyleProp; +}; + +export const arenaW = 664; +export const arenaH = 330; + +const sw = 2, + hsw = sw / 2; + +const w = 651.22, + h = 317.68; +const hw = w / 2, + hh = h / 2; + +const dw = 27, + dh = 42; +const dx = hw - dw + hsw, + dy1 = 75.93 - dh / 2 + hsw, + dy2 = dy1 + dh - sw; + +const oh = 49.84, + ox = hw + sw + hsw, + oy = -hh + 26.22 - oh / 2 + hsw; + +const ch = 47, + cc = -11.38, + cx = 282.05 + hsw, + cy = cc - ch / 2 + hsw; +const cfh = 38, + cfw = 45, + cfx = hw - cfw + hsw, + cfy1 = cc - cfh / 2 + hsw, + cfy2 = cc + cfh / 2 - hsw; + +const hs = 47, + hhs = hs / 2, + hx = 143.5, + hhh = 38 - sw, + hr = hhh / Math.sqrt(3), + hhr = hr / 2; + +const rh = 73, + rw = 44.4, + r2h = 12; +const th = 49.86, + tw = 4; + +function RampTrench({ + stroke, + fill, + transform, +}: { + stroke: string; + fill: string; + transform?: string; +}) { + return ( + + + + + + + + ); +} + +function Half({ + stroke, + fill, + transform, +}: { + stroke: string; + fill: string; + transform?: string; +}) { + return ( + + + + + + + + + + + + + + + ); +} + +export function ArenaSVG({ style }: ArenaSVGProps) { + return ( + + + + + + + + + ); +} diff --git a/components/themed/themed-view.tsx b/components/themed/themed-view.tsx index ec5c52e..108542f 100644 --- a/components/themed/themed-view.tsx +++ b/components/themed/themed-view.tsx @@ -1,8 +1,10 @@ import { View, type ViewProps } from "react-native"; import { Colors, ThemeColors } from "@/constants/theme"; +import { RefObject } from "react"; export type ThemedViewProps = ViewProps & { + ref?: RefObject; colorName?: ThemeColors; borderCol?: ThemeColors; }; diff --git a/constants/theme.ts b/constants/theme.ts index f762435..e12ba67 100644 --- a/constants/theme.ts +++ b/constants/theme.ts @@ -13,6 +13,11 @@ export const Colors = { highlight: "#ea2e2e", highlightDark: "#ad1f1f", + + blue: "#4287f5", + blueFill: "#4287f550", + red: "#f54242", + redFill: "#f5424250", }; export type ThemeColors = keyof typeof Colors;