diff --git a/src/App.js b/src/App.js index 4444bbc..f1b7c04 100644 --- a/src/App.js +++ b/src/App.js @@ -12,25 +12,187 @@ import Tabs from 'react-bootstrap/Tabs'; const MAX_INNINGS = 1 +// Handle strikes +const handleStrike = (prevState) => { + let newState = {...prevState} + let message + const newStrikes = prevState.count.strikes + 1; + var endsAtBat = false + if (newStrikes === 3) { + // Strikeout + const outResult = handleOut(newState) + endsAtBat = outResult.endsAtBat + newState = {...newState, ...outResult.newState}; + message = ["Strikeout!", outResult.message].join(' ') + } else { + newState.count.strikes = newStrikes + message = `Strike ${newStrikes}.` + } +return {newState, message, endsAtBat} +}; + +// Handle fouls +const handleFoul = (prevState) => { + let newState = {...prevState} + let message = "Foul." + + const prevStrikes = prevState.count.strikes; + if (prevStrikes === 2) { + // Nothing + } else { + newState.count.strikes = prevStrikes + 1; + } + return {newState, message} +}; + + +// Handle balls +const handleBall = (prevState) => { + const newBalls = prevState.count.balls + 1; + let newState = {...prevState} + let message = `Ball ${newBalls}.` + var endsAtBat = false + if (newBalls === 4) { + // Walk (advance batter to 1st base) + const advanceResult = advanceRunners(newState, [1,0,0,0]); + newState = { ...newState, ...resetCount(advanceResult.newState)}; + endsAtBat = true + console.log(advanceResult) + message = [message, "Walk!", advanceResult.message].join(' ') + } else { + newState.count.balls = newBalls; + } + return { newState, message, endsAtBat }; +}; + +// Handle outs +const handleOut = (prevState) => { + let newState = {...prevState} + newState = {...newState, ...resetCount(newState)} + const newOuts = newState.outs + 1; + let message = `${newOuts} out${newOuts > 1 ? "s":""}` + if (newOuts === 3) { + message += "!" + const switchInningResult = switchInning(newState); + newState = { ...newState, ...switchInningResult.newState}; + message = [message, switchInningResult.message].join(" ") + } else { + message += "." + newState = { ...newState, outs: newOuts} + } + return {newState, message, endsAtBat: true}; +}; + +const resetCount = (prevState) => { + return {...prevState, count:{strikes: 0, balls: 0}} +} + +// Advance runners and reset 1st base +export const advanceRunners = (gameState, advancements) => { + // Combine batter and runners into a single array for easier processing + var newState = {...gameState} + let runnersInclBatter = [newState.currentBatter, ...newState.bases]; + const newBasesInclBatter = [null, null, null, null]; // First, second, third, and batter + const scoredRunners = []; // Runners who score + + var message = "" + + // Recursive function to handle forced advancement + const moveRunner = (runner, targetBase) => { + if (targetBase >= 4) { + // If targetBase >= 4, the runner scores + scoredRunners.push(runner); + } else if (newBasesInclBatter[targetBase] === null) { + // If targetBase is unoccupied, place the runner there + newBasesInclBatter[targetBase] = runner; + } else { + // If targetBase is occupied, recursively move the occupier to the next base + const displacedRunner = newBasesInclBatter[targetBase]; + newBasesInclBatter[targetBase] = runner; // Place current runner here + moveRunner(displacedRunner, targetBase + 1); // Move the displaced runner + } + }; + + // Process each runner and their advancement + for (let i = 3; i >= 0; i--) { + if (runnersInclBatter[i] !== null) { + const targetBase = i + advancements[i]; + moveRunner(runnersInclBatter[i], targetBase); + } + // console.log(i, runnersInclBatter[i], runnersInclBatter) + } + newState.bases = newBasesInclBatter.slice(1); + if (scoredRunners.length > 0){message=`${scoredRunners.length} runner scored!`} + console.log(scoredRunners, scoredRunners.length) + return { newState, scoredRunners, message }; + } + +const isGameFinal = ({inning, isTopHalf, homeScore, awayScore}) => { + if (inning >= MAX_INNINGS) { + if (isTopHalf && homeScore > awayScore){ + console.log(homeScore, awayScore, 'no need for bottom of last inning') + return true; + } else if (!isTopHalf && homeScore != awayScore) { + return true; + } else { + console.log('No decision yet! Keep playing') + return false; + } + } else { + return false + } +} + +// Switch innings +const switchInning = (prevState) => { + console.dir(prevState) + let message + let newState = {...prevState, + ...resetCount(prevState), + bases: [null, null, null], + outs: 0, + isTopHalf: !prevState.isTopHalf, // Switch halves + inning: prevState.isTopHalf ? prevState.inning : prevState.inning + 1 // Increment inning after Bottom + } + if (isGameFinal(newState)){ + console.dir(newState) + newState = {...newState, isFinal: true} + message = "Game over!" + } else { + message = "Next inning." + } + return {newState, message} +}; + function App() { // Game state const [gameState, setGameState] = useState({ bases: [null, null, null], // [1st, 2nd, 3rd] + currentBatter: null, inning: 1, isTopHalf: true, // true = Top of inning (away team bats), false = Bottom (home team bats) outs: 0, - balls: 0, // Count for current at-bat - strikes: 0, // Count for current at-bat - currentBatterIndex: { away: 0, home: 0 }, // Tracks the current batter for each team - homeScore: 0, - awayScore: 0, + count: { + strikes: 0, + balls: 0 + }, + score: { + home: 0, + away: 0 + }, isFinal: false }); // Separate lineup state for both teams - const [lineups, setLineups] = useState({ - away: [], // Away team's lineup - home: [], // Home team's lineup + const [lineupState, setLineupState] = useState({ + away: { + players: [], + currentBatterIndex: 0 + }, + home: { + players: [], + currentBatterIndex: 0 + } }); const [history, setHistory] = useState([]); @@ -39,52 +201,61 @@ function App() { // Simulate fetching the lineups on component mount useEffect(() => { - const fetchLineups = async () => { + const fetchLineups = async () => ( // Simulate an API call or database query - const fetchedLineups = { + { away: ["Player A1", "Player A2", "Player A3", "Player A4", "Player A5"], home: ["Player H1", "Player H2", "Player H3", "Player H4", "Player H5"], - }; - setLineups(fetchedLineups); - }; + } + ); - fetchLineups(); + fetchLineups() + .then((fetchedLineups)=>{ + const newLineupState = { + home:{...lineupState.home, players: fetchedLineups.home}, + away:{...lineupState.away, players: fetchedLineups.away} + } + setLineupState({...newLineupState}) + return newLineupState + }) + .then((newLineupState)=>{ + const activeLineup = getActiveLineup(); + const currentIndex = getCurrentBatterIndex(); + const currentBatter = activeLineup[currentIndex]; + console.log(activeLineup, currentIndex) + setGameState({...gameState, currentBatter}); + }); }, []); // Runs only once when the component mounts // Get the current batter's index const getCurrentBatterIndex = () => gameState.isTopHalf - ? gameState.currentBatterIndex.away - : gameState.currentBatterIndex.home; + ? lineupState.away.currentBatterIndex + : lineupState.home.currentBatterIndex; // Update the batter index for the active team - const advanceLineup = () => { - setGameState((prevState) => { - const teamKey = prevState.isTopHalf ? "away" : "home"; - const nextIndex = - (prevState.currentBatterIndex[teamKey] + 1) % lineups[teamKey].length; - return { - ...prevState, - currentBatterIndex: { - ...prevState.currentBatterIndex, - [teamKey]: nextIndex, - }, - }; - }); + const advanceLineup = (prevGameState, prevLineupState, setLineupState) => { + console.log('Advancing Lineup') + const teamKey = prevGameState.isTopHalf ? "away" : "home"; + const {currentBatterIndex, players} = prevLineupState[teamKey] + var newLineupState = {...prevLineupState} + const nextBatterIndex = + (currentBatterIndex + 1) % players.length; + newLineupState[teamKey].currentBatterIndex = nextBatterIndex + setLineupState(newLineupState) }; const renderLineup = (home_or_away) => { - const lineup = lineups[home_or_away]; - const currentIndex = gameState.currentBatterIndex[home_or_away]; + const {players, currentBatterIndex} = lineupState[home_or_away]; return (
| {player} | @@ -97,7 +268,7 @@ function App() { // Function to determine which lineup is active const getActiveLineup = () => - gameState.isTopHalf ? lineups.away : lineups.home; + gameState.isTopHalf ? lineupState.away.players : lineupState.home.players; // Add an entry to the game log const addToGameLog = (message) => { @@ -106,88 +277,55 @@ function App() { }; // Function to handle play input - const handlePlay = (play) => { + const handlePitch = (pitch) => { // Save current state to history before modifying it setHistory((prevHistory) => [...prevHistory, { ...gameState }]); + // const currentBatter = getCurrentBatter(); const lineup = getActiveLineup(); const currentIndex = getCurrentBatterIndex(); - var gameLogMessage = `${lineup[currentIndex]} batting: ${play}` - console.log(`Handle play ${play}`) - switch (play) { - case "strike": - const newState = handleStrike(gameState); - gameLogMessage += ` ${newState.isStrikeOut ? "3. Strikeout!": newState.strikes}` - break; - case "ball": - handleBall(); - break; - case "out": - handleOut(); - break; - case "foul": - handleFoul(); - break; - case "hit": - advanceRunners(); - break; - default: - console.warn("Unknown play:", play); // Optional: Handle unexpected values - } - console.log('made it to end') - addToGameLog(gameLogMessage) - }; - - // Handle strikes - const handleStrike = (prevState) => { - let newState - let isStrikeOut = false - const newStrikes = prevState.strikes + 1; - if (newStrikes === 3) { - // Strikeout - handleOut(); - isStrikeOut = true - newState = { ...prevState, strikes: 0, balls: 0 }; // Reset count + const currentBatter = lineup[currentIndex]; + const gameLogMessageArray = [`${currentBatter} batting: `] + console.log(`Handle play ${pitch}`) + let pitchResultState = {...gameState} + pitchResultState.currentBatter = currentBatter + var endsAtBat + var scoredRunners + if (pitch === "strike") { + const result = handleStrike(pitchResultState) + const {newState, message} = result + endsAtBat = result.endsAtBat + gameLogMessageArray.push(message) + pitchResultState = { ...pitchResultState, ...newState }; + } else if (pitch === "foul") { + const result = handleFoul(pitchResultState) + const {newState, message} = result + endsAtBat = result.endsAtBat + gameLogMessageArray.push(message) + pitchResultState = { ...pitchResultState, ...newState}; + } else if (pitch === "ball") { + const result = handleBall(pitchResultState) + const {newState, message} = result + endsAtBat = result.endsAtBat + gameLogMessageArray.push(message) + pitchResultState = { ...pitchResultState, ...newState }; + } else if (pitch === "out") { + const result = handleOut(pitchResultState) + const {newState, message} = result + endsAtBat = result.endsAtBat + gameLogMessageArray.push(message) + pitchResultState = { ...pitchResultState, ...newState }; + } else if (pitch === "hit") { + const result = advanceRunners(pitchResultState, [1,0,0,0]); + const {newState, message} = result + gameLogMessageArray.push(message) + scoredRunners = result.scoredRunners + pitchResultState = {...pitchResultState, ...newState} } else { - newState = { ...prevState, strikes: newStrikes }; + console.warn("Unknown play:", pitch); // Optional: Handle unexpected values } - - setGameState(newState) - return {...newState, isStrikeOut} -}; - - // Handle fouls - const handleFoul = () => { - setGameState((prevState) => { - const newStrikes = prevState.strikes == 2 ? prevState.strikes : prevState.strikes + 1; - return { ...prevState, strikes: newStrikes }; - }); - }; - - // Handle balls - const handleBall = () => { - setGameState((prevState) => { - const newBalls = prevState.balls + 1; - if (newBalls === 4) { - // Walk (advance batter to 1st base) - advanceRunners(); - return { ...prevState, strikes: 0, balls: 0 }; // Reset count - } - return { ...prevState, balls: newBalls }; - }); - }; - - // Handle outs - const handleOut = () => { - advanceLineup(); // Move to the next batter - setGameState((prevState) => { - const newOuts = prevState.outs + 1; - if (newOuts === 3) { - // Reset outs and switch inning/half - switchInning(); - return { ...prevState, outs: 0, strikes: 0, balls: 0 }; // Reset count and outs - } - return { ...prevState, outs: newOuts, strikes: 0, balls: 0 }; // Reset count - }); + setGameState(pitchResultState) + if (endsAtBat){advanceLineup(gameState, lineupState, setLineupState)} + addToGameLog([...gameLogMessageArray].join(' ')) }; const scoreRunner = (scoringRunner) => { @@ -201,53 +339,6 @@ function App() { }) } - // Advance runners and reset 1st base - const advanceRunners = () => { - setGameState((prevState) => { - const newBases = [...prevState.bases]; - const activeLineup = getActiveLineup(); - const currentBatter = activeLineup[getCurrentBatterIndex()]; - - for (let i = 2; i >= 0; i--) { - if (newBases[i]) { - if (i === 2) { - scoreRunner(newBases[i]) - if (prevState.inning >= MAX_INNINGS && prevState.homeScore > prevState.awayScore) { - endGame(); - } - } else { - newBases[i + 1] = newBases[i]; - } - newBases[i] = null; // Clear current base - } - } - newBases[0] = currentBatter; // Add current batter to 1st base - advanceLineup(); // Move to the next batter - return { ...prevState, bases: newBases, strikes: 0, balls: 0 }; // Reset count - }); - }; - - // Switch innings - const switchInning = () => { - setGameState((prevState) => { - if (prevState.inning >= MAX_INNINGS) { - if (prevState.isTopHalf && prevState.homeScore > prevState.awayScore){ - endGame(); - return {...prevState}; - } else if (!prevState.isTopHalf && prevState.homeScore != prevState.awayScore) { - endGame(); - return {...prevState}; - } else { - console.log('No decision yet! Keep playing') - } - } - return {...prevState, - isTopHalf: !prevState.isTopHalf, // Switch halves - inning: prevState.isTopHalf ? prevState.inning : prevState.inning + 1, // Increment inning after Bottom - bases: [null, null, null]} - }); - }; - const endGame = () => { setGameState((prevState)=>({...prevState, isFinal: true})) } @@ -309,14 +400,14 @@ function App() { - - - - + + + + - - - + + +