refactor: extract useTrackManagement hook from SteamTrainJourney
- Create dedicated hook for track generation and positioning logic - Move 115+ lines of track/landmark/passenger display management to useTrackManagement.ts - Consolidates track generation, ties/rails, station positions, landmarks, and passenger display transitions - Reduce SteamTrainJourney.tsx from 959 to 857 lines (102 lines removed) - Preserve all functionality exactly - no behavioral changes Benefits: - Centralizes all track-related state management - Handles route transition logic in one place - Makes track generation logic easier to test - Improves overall code organization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState, useMemo, memo } from 'react'
|
||||
import { useRef, useState, useMemo, memo } from 'react'
|
||||
import { useSpring, animated } from '@react-spring/web'
|
||||
import { useSteamJourney } from '../../hooks/useSteamJourney'
|
||||
import { usePassengerAnimations, type BoardingAnimation, type DisembarkingAnimation } from '../../hooks/usePassengerAnimations'
|
||||
import { useTrainTransforms } from '../../hooks/useTrainTransforms'
|
||||
import { useTrackManagement } from '../../hooks/useTrackManagement'
|
||||
import { useComplementRace } from '../../context/ComplementRaceContext'
|
||||
import { RailroadTrackGenerator } from '../../lib/RailroadTrackGenerator'
|
||||
import { PassengerCard } from '../PassengerCard'
|
||||
import { getRouteTheme } from '../../lib/routeThemes'
|
||||
import { generateLandmarks, type Landmark } from '../../lib/landmarks'
|
||||
import { PressureGauge } from '../PressureGauge'
|
||||
import { useGameMode } from '@/contexts/GameModeContext'
|
||||
import { useUserProfile } from '@/contexts/UserProfileContext'
|
||||
import type { Passenger } from '../../lib/gameTypes'
|
||||
|
||||
const BoardingPassengerAnimation = memo(({ animation }: { animation: BoardingAnimation }) => {
|
||||
const spring = useSpring({
|
||||
@@ -96,15 +95,16 @@ export function SteamTrainJourney({ momentum, trainPosition, pressure, elapsedTi
|
||||
const svgRef = useRef<SVGSVGElement>(null)
|
||||
const pathRef = useRef<SVGPathElement>(null)
|
||||
const [trackGenerator] = useState(() => new RailroadTrackGenerator(800, 600))
|
||||
const [trackData, setTrackData] = useState<ReturnType<typeof trackGenerator.generateTrack> | null>(null)
|
||||
const [tiesAndRails, setTiesAndRails] = useState<{
|
||||
ties: Array<{ x1: number; y1: number; x2: number; y2: number }>
|
||||
leftRailPoints: string[]
|
||||
rightRailPoints: string[]
|
||||
} | null>(null)
|
||||
const [stationPositions, setStationPositions] = useState<Array<{ x: number; y: number }>>([])
|
||||
const [landmarks, setLandmarks] = useState<Landmark[]>([])
|
||||
const [landmarkPositions, setLandmarkPositions] = useState<Array<{ x: number; y: number }>>([])
|
||||
|
||||
// Track management (extracted to hook)
|
||||
const { trackData, tiesAndRails, stationPositions, landmarks, landmarkPositions, displayPassengers } = useTrackManagement({
|
||||
currentRoute: state.currentRoute,
|
||||
trainPosition,
|
||||
trackGenerator,
|
||||
pathRef,
|
||||
stations: state.stations,
|
||||
passengers: state.passengers
|
||||
})
|
||||
|
||||
// Train transforms (extracted to hook)
|
||||
const { trainTransform, trainCars, locomotiveOpacity, maxCars, carSpacing } = useTrainTransforms({
|
||||
@@ -123,12 +123,6 @@ export function SteamTrainJourney({ momentum, trainPosition, pressure, elapsedTi
|
||||
pathRef
|
||||
})
|
||||
|
||||
// Generate landmarks when route changes
|
||||
useEffect(() => {
|
||||
const newLandmarks = generateLandmarks(state.currentRoute)
|
||||
setLandmarks(newLandmarks)
|
||||
}, [state.currentRoute])
|
||||
|
||||
// Time remaining (60 seconds total)
|
||||
const timeRemaining = Math.max(0, 60 - Math.floor(elapsedTime / 1000))
|
||||
|
||||
@@ -138,102 +132,6 @@ export function SteamTrainJourney({ momentum, trainPosition, pressure, elapsedTi
|
||||
// Get current route theme
|
||||
const routeTheme = getRouteTheme(state.currentRoute)
|
||||
|
||||
// Track previous route data to maintain visuals during transition
|
||||
const previousRouteRef = useRef(state.currentRoute)
|
||||
const [pendingTrackData, setPendingTrackData] = useState<ReturnType<typeof trackGenerator.generateTrack> | null>(null)
|
||||
|
||||
// Preserve passengers during route transition
|
||||
const [displayPassengers, setDisplayPassengers] = useState(state.passengers)
|
||||
|
||||
// Generate track on mount and when route changes
|
||||
useEffect(() => {
|
||||
const track = trackGenerator.generateTrack(state.currentRoute)
|
||||
|
||||
// If we're in the middle of a route (position > 0), store as pending
|
||||
// Only apply new track when position resets to beginning (< 0)
|
||||
if (state.trainPosition > 0 && previousRouteRef.current !== state.currentRoute) {
|
||||
setPendingTrackData(track)
|
||||
} else {
|
||||
setTrackData(track)
|
||||
previousRouteRef.current = state.currentRoute
|
||||
setPendingTrackData(null)
|
||||
}
|
||||
}, [trackGenerator, state.currentRoute, state.trainPosition])
|
||||
|
||||
// Apply pending track when train resets to beginning
|
||||
useEffect(() => {
|
||||
if (pendingTrackData && state.trainPosition < 0) {
|
||||
setTrackData(pendingTrackData)
|
||||
previousRouteRef.current = state.currentRoute
|
||||
setPendingTrackData(null)
|
||||
}
|
||||
}, [pendingTrackData, state.trainPosition, state.currentRoute])
|
||||
|
||||
// Manage passenger display during route transitions
|
||||
useEffect(() => {
|
||||
// If we're starting a new route (position < 0) or passengers haven't changed, update immediately
|
||||
if (state.trainPosition < 0 || state.passengers === previousPassengersRef.current) {
|
||||
setDisplayPassengers(state.passengers)
|
||||
previousPassengersRef.current = state.passengers
|
||||
}
|
||||
// Otherwise, if we're mid-route and passengers changed, keep showing old passengers
|
||||
else if (state.trainPosition > 0 && state.passengers !== previousPassengersRef.current) {
|
||||
// Keep displaying old passengers until train exits
|
||||
// Don't update displayPassengers yet
|
||||
}
|
||||
|
||||
// When train resets to beginning, switch to new passengers
|
||||
if (state.trainPosition < 0 && state.passengers !== previousPassengersRef.current) {
|
||||
setDisplayPassengers(state.passengers)
|
||||
previousPassengersRef.current = state.passengers
|
||||
}
|
||||
}, [state.passengers, state.trainPosition])
|
||||
|
||||
// Update display passengers during gameplay (same route)
|
||||
useEffect(() => {
|
||||
// Only update if we're in the same route (not transitioning)
|
||||
if (previousRouteRef.current === state.currentRoute && state.trainPosition >= 0 && state.trainPosition < 100) {
|
||||
setDisplayPassengers(state.passengers)
|
||||
}
|
||||
}, [state.passengers, state.currentRoute, state.trainPosition])
|
||||
|
||||
// Generate ties and rails when path is ready
|
||||
useEffect(() => {
|
||||
if (pathRef.current && trackData) {
|
||||
const result = trackGenerator.generateTiesAndRails(pathRef.current)
|
||||
setTiesAndRails(result)
|
||||
}
|
||||
}, [trackData, trackGenerator])
|
||||
|
||||
// Calculate station positions when path is ready
|
||||
useEffect(() => {
|
||||
if (pathRef.current) {
|
||||
const positions = state.stations.map(station => {
|
||||
const pathLength = pathRef.current!.getTotalLength()
|
||||
const distance = (station.position / 100) * pathLength
|
||||
const point = pathRef.current!.getPointAtLength(distance)
|
||||
return { x: point.x, y: point.y }
|
||||
})
|
||||
setStationPositions(positions)
|
||||
}
|
||||
}, [trackData, state.stations])
|
||||
|
||||
// Calculate landmark positions when path is ready
|
||||
useEffect(() => {
|
||||
if (pathRef.current && landmarks.length > 0) {
|
||||
const positions = landmarks.map(landmark => {
|
||||
const pathLength = pathRef.current!.getTotalLength()
|
||||
const distance = (landmark.position / 100) * pathLength
|
||||
const point = pathRef.current!.getPointAtLength(distance)
|
||||
return {
|
||||
x: point.x + landmark.offset.x,
|
||||
y: point.y + landmark.offset.y
|
||||
}
|
||||
})
|
||||
setLandmarkPositions(positions)
|
||||
}
|
||||
}, [trackData, landmarks])
|
||||
|
||||
|
||||
// Memoize filtered passenger lists to avoid recalculating on every render
|
||||
const boardedPassengers = useMemo(() =>
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import type { RailroadTrackGenerator } from '../lib/RailroadTrackGenerator'
|
||||
import type { Station, Passenger } from '../lib/gameTypes'
|
||||
import { generateLandmarks, type Landmark } from '../lib/landmarks'
|
||||
|
||||
interface UseTrackManagementParams {
|
||||
currentRoute: number
|
||||
trainPosition: number
|
||||
trackGenerator: RailroadTrackGenerator
|
||||
pathRef: React.RefObject<SVGPathElement>
|
||||
stations: Station[]
|
||||
passengers: Passenger[]
|
||||
}
|
||||
|
||||
export function useTrackManagement({
|
||||
currentRoute,
|
||||
trainPosition,
|
||||
trackGenerator,
|
||||
pathRef,
|
||||
stations,
|
||||
passengers
|
||||
}: UseTrackManagementParams) {
|
||||
const [trackData, setTrackData] = useState<ReturnType<typeof trackGenerator.generateTrack> | null>(null)
|
||||
const [tiesAndRails, setTiesAndRails] = useState<{
|
||||
ties: Array<{ x1: number; y1: number; x2: number; y2: number }>
|
||||
leftRailPoints: string[]
|
||||
rightRailPoints: string[]
|
||||
} | null>(null)
|
||||
const [stationPositions, setStationPositions] = useState<Array<{ x: number; y: number }>>([])
|
||||
const [landmarks, setLandmarks] = useState<Landmark[]>([])
|
||||
const [landmarkPositions, setLandmarkPositions] = useState<Array<{ x: number; y: number }>>([])
|
||||
const [displayPassengers, setDisplayPassengers] = useState<Passenger[]>(passengers)
|
||||
|
||||
// Track previous route data to maintain visuals during transition
|
||||
const previousRouteRef = useRef(currentRoute)
|
||||
const [pendingTrackData, setPendingTrackData] = useState<ReturnType<typeof trackGenerator.generateTrack> | null>(null)
|
||||
const previousPassengersRef = useRef<Passenger[]>(passengers)
|
||||
|
||||
// Generate landmarks when route changes
|
||||
useEffect(() => {
|
||||
const newLandmarks = generateLandmarks(currentRoute)
|
||||
setLandmarks(newLandmarks)
|
||||
}, [currentRoute])
|
||||
|
||||
// Generate track on mount and when route changes
|
||||
useEffect(() => {
|
||||
const track = trackGenerator.generateTrack(currentRoute)
|
||||
|
||||
// If we're in the middle of a route (position > 0), store as pending
|
||||
// Only apply new track when position resets to beginning (< 0)
|
||||
if (trainPosition > 0 && previousRouteRef.current !== currentRoute) {
|
||||
setPendingTrackData(track)
|
||||
} else {
|
||||
setTrackData(track)
|
||||
previousRouteRef.current = currentRoute
|
||||
setPendingTrackData(null)
|
||||
}
|
||||
}, [trackGenerator, currentRoute, trainPosition])
|
||||
|
||||
// Apply pending track when train resets to beginning
|
||||
useEffect(() => {
|
||||
if (pendingTrackData && trainPosition < 0) {
|
||||
setTrackData(pendingTrackData)
|
||||
previousRouteRef.current = currentRoute
|
||||
setPendingTrackData(null)
|
||||
}
|
||||
}, [pendingTrackData, trainPosition, currentRoute])
|
||||
|
||||
// Manage passenger display during route transitions
|
||||
useEffect(() => {
|
||||
// If we're starting a new route (position < 0) or passengers haven't changed, update immediately
|
||||
if (trainPosition < 0 || passengers === previousPassengersRef.current) {
|
||||
setDisplayPassengers(passengers)
|
||||
previousPassengersRef.current = passengers
|
||||
}
|
||||
// Otherwise, if we're mid-route and passengers changed, keep showing old passengers
|
||||
else if (trainPosition > 0 && passengers !== previousPassengersRef.current) {
|
||||
// Keep displaying old passengers until train exits
|
||||
// Don't update displayPassengers yet
|
||||
}
|
||||
|
||||
// When train resets to beginning, switch to new passengers
|
||||
if (trainPosition < 0 && passengers !== previousPassengersRef.current) {
|
||||
setDisplayPassengers(passengers)
|
||||
previousPassengersRef.current = passengers
|
||||
}
|
||||
}, [passengers, trainPosition])
|
||||
|
||||
// Update display passengers during gameplay (same route)
|
||||
useEffect(() => {
|
||||
// Only update if we're in the same route (not transitioning)
|
||||
if (previousRouteRef.current === currentRoute && trainPosition >= 0 && trainPosition < 100) {
|
||||
setDisplayPassengers(passengers)
|
||||
}
|
||||
}, [passengers, currentRoute, trainPosition])
|
||||
|
||||
// Generate ties and rails when path is ready
|
||||
useEffect(() => {
|
||||
if (pathRef.current && trackData) {
|
||||
const result = trackGenerator.generateTiesAndRails(pathRef.current)
|
||||
setTiesAndRails(result)
|
||||
}
|
||||
}, [trackData, trackGenerator, pathRef])
|
||||
|
||||
// Calculate station positions when path is ready
|
||||
useEffect(() => {
|
||||
if (pathRef.current) {
|
||||
const positions = stations.map(station => {
|
||||
const pathLength = pathRef.current!.getTotalLength()
|
||||
const distance = (station.position / 100) * pathLength
|
||||
const point = pathRef.current!.getPointAtLength(distance)
|
||||
return { x: point.x, y: point.y }
|
||||
})
|
||||
setStationPositions(positions)
|
||||
}
|
||||
}, [trackData, stations, pathRef])
|
||||
|
||||
// Calculate landmark positions when path is ready
|
||||
useEffect(() => {
|
||||
if (pathRef.current && landmarks.length > 0) {
|
||||
const positions = landmarks.map(landmark => {
|
||||
const pathLength = pathRef.current!.getTotalLength()
|
||||
const distance = (landmark.position / 100) * pathLength
|
||||
const point = pathRef.current!.getPointAtLength(distance)
|
||||
return {
|
||||
x: point.x + landmark.offset.x,
|
||||
y: point.y + landmark.offset.y
|
||||
}
|
||||
})
|
||||
setLandmarkPositions(positions)
|
||||
}
|
||||
}, [trackData, landmarks, pathRef])
|
||||
|
||||
return {
|
||||
trackData,
|
||||
tiesAndRails,
|
||||
stationPositions,
|
||||
landmarks,
|
||||
landmarkPositions,
|
||||
displayPassengers
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user