Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/openflight/launch_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ class ClubType(Enum):
DRIVER = "driver"
WOOD_3 = "3-wood"
WOOD_5 = "5-wood"
HYBRID = "hybrid"
WOOD_7 = "7-wood"
HYBRID_3 = "3-hybrid"
HYBRID_5 = "5-hybrid"
HYBRID_7 = "7-hybrid"
HYBRID_9 = "9-hybrid"
IRON_2 = "2-iron"
IRON_3 = "3-iron"
IRON_4 = "4-iron"
IRON_5 = "5-iron"
Expand All @@ -31,6 +36,9 @@ class ClubType(Enum):
IRON_8 = "8-iron"
IRON_9 = "9-iron"
PW = "pw"
GW = "gw"
SW = "sw"
LW = "lw"
UNKNOWN = "unknown"


Expand Down Expand Up @@ -85,7 +93,12 @@ def estimate_carry_distance(ball_speed_mph: float, club: ClubType = ClubType.DRI
ClubType.DRIVER: 1.0,
ClubType.WOOD_3: 0.96, # Slightly less efficient
ClubType.WOOD_5: 0.93,
ClubType.HYBRID: 0.90,
ClubType.WOOD_7: 0.91,
ClubType.HYBRID_3: 0.91,
ClubType.HYBRID_5: 0.89,
ClubType.HYBRID_7: 0.87,
ClubType.HYBRID_9: 0.85,
ClubType.IRON_2: 0.88,
ClubType.IRON_3: 0.87,
ClubType.IRON_4: 0.85,
ClubType.IRON_5: 0.82,
Expand All @@ -94,6 +107,9 @@ def estimate_carry_distance(ball_speed_mph: float, club: ClubType = ClubType.DRI
ClubType.IRON_8: 0.73,
ClubType.IRON_9: 0.70,
ClubType.PW: 0.67,
ClubType.GW: 0.64,
ClubType.SW: 0.62,
ClubType.LW: 0.61,
ClubType.UNKNOWN: 1.0,
}

Expand Down
20 changes: 18 additions & 2 deletions src/openflight/rolling_buffer/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ def get_optimal_spin_for_ball_speed(ball_speed_mph: float, club: ClubType = Club
ClubType.DRIVER: 1.0,
ClubType.WOOD_3: 1.15,
ClubType.WOOD_5: 1.25,
ClubType.HYBRID: 1.4,
ClubType.WOOD_7: 1.32,
ClubType.HYBRID_3: 1.45,
ClubType.HYBRID_5: 1.55,
ClubType.HYBRID_7: 1.65,
ClubType.HYBRID_9: 1.75,
ClubType.IRON_2: 1.5,
ClubType.IRON_3: 1.6,
ClubType.IRON_4: 1.8,
ClubType.IRON_5: 2.0,
Expand All @@ -74,6 +79,9 @@ def get_optimal_spin_for_ball_speed(ball_speed_mph: float, club: ClubType = Club
ClubType.IRON_8: 2.8,
ClubType.IRON_9: 3.2,
ClubType.PW: 3.6,
ClubType.GW: 4.1,
ClubType.SW: 4.3,
ClubType.LW: 4.6,
ClubType.UNKNOWN: 1.0,
}

Expand Down Expand Up @@ -157,7 +165,12 @@ def estimate_carry_with_spin(
ClubType.DRIVER: 1.48,
ClubType.WOOD_3: 1.44,
ClubType.WOOD_5: 1.42,
ClubType.HYBRID: 1.38,
ClubType.WOOD_7: 1.41,
ClubType.HYBRID_3: 1.39,
ClubType.HYBRID_5: 1.37,
ClubType.HYBRID_7: 1.35,
ClubType.HYBRID_9: 1.33,
ClubType.IRON_2: 1.36,
ClubType.IRON_3: 1.35,
ClubType.IRON_4: 1.33,
ClubType.IRON_5: 1.31,
Expand All @@ -166,6 +179,9 @@ def estimate_carry_with_spin(
ClubType.IRON_8: 1.25,
ClubType.IRON_9: 1.23,
ClubType.PW: 1.21,
ClubType.GW: 1.19,
ClubType.SW: 1.18,
ClubType.LW: 1.17,
ClubType.UNKNOWN: 1.35,
}

Expand Down
10 changes: 9 additions & 1 deletion src/openflight/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,12 @@ def simulate_shot(self, ball_speed: float = None):
ClubType.DRIVER: (143, 12, 1.45),
ClubType.WOOD_3: (135, 10, 1.42),
ClubType.WOOD_5: (128, 10, 1.40),
ClubType.HYBRID: (122, 9, 1.38),
ClubType.WOOD_7: (122, 9, 1.40),
ClubType.HYBRID_3: (123, 9, 1.39),
ClubType.HYBRID_5: (118, 9, 1.37),
ClubType.HYBRID_7: (112, 8, 1.35),
ClubType.HYBRID_9: (106, 8, 1.33),
ClubType.IRON_2: (120, 9, 1.35),
ClubType.IRON_3: (118, 9, 1.35),
ClubType.IRON_4: (114, 8, 1.33),
ClubType.IRON_5: (110, 8, 1.31),
Expand All @@ -771,6 +776,9 @@ def simulate_shot(self, ball_speed: float = None):
ClubType.IRON_8: (94, 6, 1.25),
ClubType.IRON_9: (88, 6, 1.23),
ClubType.PW: (82, 5, 1.21),
ClubType.GW: (76, 5, 1.20),
ClubType.SW: (73, 5, 1.19),
ClubType.LW: (70, 5, 1.18),
ClubType.UNKNOWN: (120, 15, 1.35),
}

Expand Down
102 changes: 57 additions & 45 deletions ui/src/components/ClubPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import { useState } from 'react';
import './ClubPicker.css';

const CLUBS = [
// Irons (3-PW)
{ id: '3-iron', label: '3i' },
{ id: '4-iron', label: '4i' },
{ id: '5-iron', label: '5i' },
{ id: '6-iron', label: '6i' },
{ id: '7-iron', label: '7i' },
{ id: '8-iron', label: '8i' },
{ id: '9-iron', label: '9i' },
{ id: 'pw', label: 'PW' },
// Woods (Driver, 3W, 5W)
{ id: 'driver', label: 'DR' },
{ id: '3-wood', label: '3W' },
{ id: '5-wood', label: '5W' },
];
const CLUBS_BY_TYPE = {
Irons: [
{ id: '2-iron', label: '2i' },
{ id: '3-iron', label: '3i' },
{ id: '4-iron', label: '4i' },
{ id: '5-iron', label: '5i' },
{ id: '6-iron', label: '6i' },
{ id: '7-iron', label: '7i' },
{ id: '8-iron', label: '8i' },
{ id: '9-iron', label: '9i' },
{ id: 'pw', label: 'PW' },
{ id: 'gw', label: 'GW' },
{ id: 'sw', label: 'SW' },
{ id: 'lw', label: 'LW' },
],
Hybrids: [
{ id: '3-hybrid', label: '3H' },
{ id: '5-hybrid', label: '5H' },
{ id: '7-hybrid', label: '7H' },
{ id: '9-hybrid', label: '9H' },
],
Woods: [
{ id: 'driver', label: 'DR' },
{ id: '3-wood', label: '3W' },
{ id: '5-wood', label: '5W' },
{ id: '7-wood', label: '7W' },
],
};
const ALL_CLUBS = Object.values(CLUBS_BY_TYPE).flat();

interface ClubPickerProps {
selectedClub: string;
Expand All @@ -25,7 +39,8 @@ interface ClubPickerProps {
export function ClubPicker({ selectedClub, onClubChange }: ClubPickerProps) {
const [isOpen, setIsOpen] = useState(false);

const selectedLabel = CLUBS.find((c) => c.id === selectedClub)?.label || 'DR';
const selectedLabel =
ALL_CLUBS.find(c => c.id === selectedClub)?.label || 'DR';

const handleSelect = (clubId: string) => {
onClubChange(clubId);
Expand All @@ -42,7 +57,9 @@ export function ClubPicker({ selectedClub, onClubChange }: ClubPickerProps) {
<span className="club-picker__label">Club</span>
<span className="club-picker__value">{selectedLabel}</span>
<svg
className={`club-picker__arrow ${isOpen ? 'club-picker__arrow--open' : ''}`}
className={`club-picker__arrow ${
isOpen ? 'club-picker__arrow--open' : ''
}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
Expand All @@ -54,36 +71,31 @@ export function ClubPicker({ selectedClub, onClubChange }: ClubPickerProps) {

{isOpen && (
<>
<div className="club-picker__overlay" onClick={() => setIsOpen(false)} />
<div
className="club-picker__overlay"
onClick={() => setIsOpen(false)}
/>
<div className="club-picker__dropdown">
<div className="club-picker__section">
<span className="club-picker__section-title">Irons</span>
<div className="club-picker__grid">
{CLUBS.slice(0, 8).map((club) => (
<button
key={club.id}
className={`club-picker__option ${selectedClub === club.id ? 'club-picker__option--selected' : ''}`}
onClick={() => handleSelect(club.id)}
>
{club.label}
</button>
))}
{Object.entries(CLUBS_BY_TYPE).map(([type, clubs]) => (
<div className="club-picker__section">
<span className="club-picker__section-title">{type}</span>
<div className="club-picker__grid">
{clubs.map(club => (
<button
key={club.id}
className={`club-picker__option ${
selectedClub === club.id
? 'club-picker__option--selected'
: ''
}`}
onClick={() => handleSelect(club.id)}
>
{club.label}
</button>
))}
</div>
</div>
</div>
<div className="club-picker__section">
<span className="club-picker__section-title">Woods</span>
<div className="club-picker__grid">
{CLUBS.slice(8).map((club) => (
<button
key={club.id}
className={`club-picker__option ${selectedClub === club.id ? 'club-picker__option--selected' : ''}`}
onClick={() => handleSelect(club.id)}
>
{club.label}
</button>
))}
</div>
</div>
))}
</div>
</>
)}
Expand Down