Refactor game logic and improve state management, add some tests
- Added `handleHit` function to handle hit types (single, double, triple, home run). - Enhanced `handleBall` and `handlePitch` to track `scoredRunners` and update scores dynamically. - Updated history to include both `gameState` and `lineupState`. - Refactored UI components (`GameStateDisplay`) to use unified score structure and display detailed base states. - Styled active lineup tab with visual emphasis. - Introduced utility functions `encodeInning` and `decodeInning` for inning state management.
This commit is contained in:
419
src/App.js
419
src/App.js
@@ -12,25 +12,187 @@ import Tabs from 'react-bootstrap/Tabs';
|
|||||||
|
|
||||||
const MAX_INNINGS = 1
|
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() {
|
function App() {
|
||||||
// Game state
|
// Game state
|
||||||
const [gameState, setGameState] = useState({
|
const [gameState, setGameState] = useState({
|
||||||
bases: [null, null, null], // [1st, 2nd, 3rd]
|
bases: [null, null, null], // [1st, 2nd, 3rd]
|
||||||
|
currentBatter: null,
|
||||||
inning: 1,
|
inning: 1,
|
||||||
isTopHalf: true, // true = Top of inning (away team bats), false = Bottom (home team bats)
|
isTopHalf: true, // true = Top of inning (away team bats), false = Bottom (home team bats)
|
||||||
outs: 0,
|
outs: 0,
|
||||||
balls: 0, // Count for current at-bat
|
count: {
|
||||||
strikes: 0, // Count for current at-bat
|
strikes: 0,
|
||||||
currentBatterIndex: { away: 0, home: 0 }, // Tracks the current batter for each team
|
balls: 0
|
||||||
homeScore: 0,
|
},
|
||||||
awayScore: 0,
|
score: {
|
||||||
|
home: 0,
|
||||||
|
away: 0
|
||||||
|
},
|
||||||
isFinal: false
|
isFinal: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Separate lineup state for both teams
|
// Separate lineup state for both teams
|
||||||
const [lineups, setLineups] = useState({
|
const [lineupState, setLineupState] = useState({
|
||||||
away: [], // Away team's lineup
|
away: {
|
||||||
home: [], // Home team's lineup
|
players: [],
|
||||||
|
currentBatterIndex: 0
|
||||||
|
},
|
||||||
|
home: {
|
||||||
|
players: [],
|
||||||
|
currentBatterIndex: 0
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [history, setHistory] = useState([]);
|
const [history, setHistory] = useState([]);
|
||||||
@@ -39,52 +201,61 @@ function App() {
|
|||||||
|
|
||||||
// Simulate fetching the lineups on component mount
|
// Simulate fetching the lineups on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLineups = async () => {
|
const fetchLineups = async () => (
|
||||||
// Simulate an API call or database query
|
// Simulate an API call or database query
|
||||||
const fetchedLineups = {
|
{
|
||||||
away: ["Player A1", "Player A2", "Player A3", "Player A4", "Player A5"],
|
away: ["Player A1", "Player A2", "Player A3", "Player A4", "Player A5"],
|
||||||
home: ["Player H1", "Player H2", "Player H3", "Player H4", "Player H5"],
|
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
|
}, []); // Runs only once when the component mounts
|
||||||
|
|
||||||
// Get the current batter's index
|
// Get the current batter's index
|
||||||
const getCurrentBatterIndex = () =>
|
const getCurrentBatterIndex = () =>
|
||||||
gameState.isTopHalf
|
gameState.isTopHalf
|
||||||
? gameState.currentBatterIndex.away
|
? lineupState.away.currentBatterIndex
|
||||||
: gameState.currentBatterIndex.home;
|
: lineupState.home.currentBatterIndex;
|
||||||
|
|
||||||
// Update the batter index for the active team
|
// Update the batter index for the active team
|
||||||
const advanceLineup = () => {
|
const advanceLineup = (prevGameState, prevLineupState, setLineupState) => {
|
||||||
setGameState((prevState) => {
|
console.log('Advancing Lineup')
|
||||||
const teamKey = prevState.isTopHalf ? "away" : "home";
|
const teamKey = prevGameState.isTopHalf ? "away" : "home";
|
||||||
const nextIndex =
|
const {currentBatterIndex, players} = prevLineupState[teamKey]
|
||||||
(prevState.currentBatterIndex[teamKey] + 1) % lineups[teamKey].length;
|
var newLineupState = {...prevLineupState}
|
||||||
return {
|
const nextBatterIndex =
|
||||||
...prevState,
|
(currentBatterIndex + 1) % players.length;
|
||||||
currentBatterIndex: {
|
newLineupState[teamKey].currentBatterIndex = nextBatterIndex
|
||||||
...prevState.currentBatterIndex,
|
setLineupState(newLineupState)
|
||||||
[teamKey]: nextIndex,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderLineup = (home_or_away) => {
|
const renderLineup = (home_or_away) => {
|
||||||
const lineup = lineups[home_or_away];
|
const {players, currentBatterIndex} = lineupState[home_or_away];
|
||||||
const currentIndex = gameState.currentBatterIndex[home_or_away];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{lineup.map((player, index) => (
|
{players.map((player, index) => (
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
key={index}
|
key={index}
|
||||||
className={`lineup-item ${index === currentIndex ? "at-bat" : ""}`}
|
className={`lineup-item ${index === currentBatterIndex ? "at-bat" : ""}`}
|
||||||
>
|
>
|
||||||
{player}
|
{player}
|
||||||
</td>
|
</td>
|
||||||
@@ -97,7 +268,7 @@ function App() {
|
|||||||
|
|
||||||
// Function to determine which lineup is active
|
// Function to determine which lineup is active
|
||||||
const getActiveLineup = () =>
|
const getActiveLineup = () =>
|
||||||
gameState.isTopHalf ? lineups.away : lineups.home;
|
gameState.isTopHalf ? lineupState.away.players : lineupState.home.players;
|
||||||
|
|
||||||
// Add an entry to the game log
|
// Add an entry to the game log
|
||||||
const addToGameLog = (message) => {
|
const addToGameLog = (message) => {
|
||||||
@@ -106,88 +277,55 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Function to handle play input
|
// Function to handle play input
|
||||||
const handlePlay = (play) => {
|
const handlePitch = (pitch) => {
|
||||||
// Save current state to history before modifying it
|
// Save current state to history before modifying it
|
||||||
setHistory((prevHistory) => [...prevHistory, { ...gameState }]);
|
setHistory((prevHistory) => [...prevHistory, { ...gameState }]);
|
||||||
|
// const currentBatter = getCurrentBatter();
|
||||||
const lineup = getActiveLineup();
|
const lineup = getActiveLineup();
|
||||||
const currentIndex = getCurrentBatterIndex();
|
const currentIndex = getCurrentBatterIndex();
|
||||||
var gameLogMessage = `${lineup[currentIndex]} batting: ${play}`
|
const currentBatter = lineup[currentIndex];
|
||||||
console.log(`Handle play ${play}`)
|
const gameLogMessageArray = [`${currentBatter} batting: `]
|
||||||
switch (play) {
|
console.log(`Handle play ${pitch}`)
|
||||||
case "strike":
|
let pitchResultState = {...gameState}
|
||||||
const newState = handleStrike(gameState);
|
pitchResultState.currentBatter = currentBatter
|
||||||
gameLogMessage += ` ${newState.isStrikeOut ? "3. Strikeout!": newState.strikes}`
|
var endsAtBat
|
||||||
break;
|
var scoredRunners
|
||||||
case "ball":
|
if (pitch === "strike") {
|
||||||
handleBall();
|
const result = handleStrike(pitchResultState)
|
||||||
break;
|
const {newState, message} = result
|
||||||
case "out":
|
endsAtBat = result.endsAtBat
|
||||||
handleOut();
|
gameLogMessageArray.push(message)
|
||||||
break;
|
pitchResultState = { ...pitchResultState, ...newState };
|
||||||
case "foul":
|
} else if (pitch === "foul") {
|
||||||
handleFoul();
|
const result = handleFoul(pitchResultState)
|
||||||
break;
|
const {newState, message} = result
|
||||||
case "hit":
|
endsAtBat = result.endsAtBat
|
||||||
advanceRunners();
|
gameLogMessageArray.push(message)
|
||||||
break;
|
pitchResultState = { ...pitchResultState, ...newState};
|
||||||
default:
|
} else if (pitch === "ball") {
|
||||||
console.warn("Unknown play:", play); // Optional: Handle unexpected values
|
const result = handleBall(pitchResultState)
|
||||||
}
|
const {newState, message} = result
|
||||||
console.log('made it to end')
|
endsAtBat = result.endsAtBat
|
||||||
addToGameLog(gameLogMessage)
|
gameLogMessageArray.push(message)
|
||||||
};
|
pitchResultState = { ...pitchResultState, ...newState };
|
||||||
|
} else if (pitch === "out") {
|
||||||
// Handle strikes
|
const result = handleOut(pitchResultState)
|
||||||
const handleStrike = (prevState) => {
|
const {newState, message} = result
|
||||||
let newState
|
endsAtBat = result.endsAtBat
|
||||||
let isStrikeOut = false
|
gameLogMessageArray.push(message)
|
||||||
const newStrikes = prevState.strikes + 1;
|
pitchResultState = { ...pitchResultState, ...newState };
|
||||||
if (newStrikes === 3) {
|
} else if (pitch === "hit") {
|
||||||
// Strikeout
|
const result = advanceRunners(pitchResultState, [1,0,0,0]);
|
||||||
handleOut();
|
const {newState, message} = result
|
||||||
isStrikeOut = true
|
gameLogMessageArray.push(message)
|
||||||
newState = { ...prevState, strikes: 0, balls: 0 }; // Reset count
|
scoredRunners = result.scoredRunners
|
||||||
|
pitchResultState = {...pitchResultState, ...newState}
|
||||||
} else {
|
} else {
|
||||||
newState = { ...prevState, strikes: newStrikes };
|
console.warn("Unknown play:", pitch); // Optional: Handle unexpected values
|
||||||
}
|
}
|
||||||
|
setGameState(pitchResultState)
|
||||||
setGameState(newState)
|
if (endsAtBat){advanceLineup(gameState, lineupState, setLineupState)}
|
||||||
return {...newState, isStrikeOut}
|
addToGameLog([...gameLogMessageArray].join(' '))
|
||||||
};
|
|
||||||
|
|
||||||
// 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
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const scoreRunner = (scoringRunner) => {
|
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 = () => {
|
const endGame = () => {
|
||||||
setGameState((prevState)=>({...prevState, isFinal: true}))
|
setGameState((prevState)=>({...prevState, isFinal: true}))
|
||||||
}
|
}
|
||||||
@@ -309,14 +400,14 @@ function App() {
|
|||||||
<Button className="" onClick={()=>handleUndo()} disabled={isUndoDisabled}>
|
<Button className="" onClick={()=>handleUndo()} disabled={isUndoDisabled}>
|
||||||
<FontAwesomeIcon icon={faUndo} />
|
<FontAwesomeIcon icon={faUndo} />
|
||||||
</Button>
|
</Button>
|
||||||
<button className="play strike" onClick={() => handlePlay("strike")} disabled={gameState.isFinal}>Strike</button>
|
<button className="play strike" onClick={() => handlePitch("strike")} disabled={gameState.isFinal}>Strike</button>
|
||||||
<button className="play ball" onClick={() => handlePlay("ball")} disabled={gameState.isFinal}>Ball</button>
|
<button className="play ball" onClick={() => handlePitch("ball")} disabled={gameState.isFinal}>Ball</button>
|
||||||
<button className="play foul" onClick={() => handlePlay("foul")} disabled={gameState.isFinal}>Foul</button>
|
<button className="play foul" onClick={() => handlePitch("foul")} disabled={gameState.isFinal}>Foul</button>
|
||||||
<button className="play out" onClick={() => handlePlay("out")} disabled={gameState.isFinal}>Out</button>
|
<button className="play out" onClick={() => handlePitch("out")} disabled={gameState.isFinal}>Out</button>
|
||||||
|
|
||||||
<Button className="play hit" onClick={() => handlePlay("hit")}>1</Button>
|
<Button className="play hit" onClick={() => handlePitch("hit")}>1</Button>
|
||||||
<Button className="play hit" onClick={() => handlePlay("hit")}>2</Button>
|
<Button className="play hit" onClick={() => handlePitch("hit")}>2</Button>
|
||||||
<Button className="play hit" onClick={() => handlePlay("hit")}>3</Button>
|
<Button className="play hit" onClick={() => handlePitch("hit")}>3</Button>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
142
src/App.test.js
142
src/App.test.js
@@ -1,8 +1,140 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import { advanceRunners } from './App.js';
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
// test('renders learn react link', () => {
|
||||||
render(<App />);
|
// render(<App />);
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
// const linkElement = screen.getByText(/learn react/i);
|
||||||
expect(linkElement).toBeInTheDocument();
|
// expect(linkElement).toBeInTheDocument();
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
describe('advanceRunners', () => {
|
||||||
|
it('should advance the batter only on a walk', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: ["Player2", null, null], currentBatter: "Batter"}
|
||||||
|
const advancements = [1, 0, 0, 0];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: ["Batter", "Player2", null],
|
||||||
|
scoredRunners: [],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should advance runners correctly with forced advancement', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: ["Player2", "Player1", null], currentBatter: "Batter"}
|
||||||
|
const advancements = [1, 0, 0, 0];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: ["Batter", "Player2", "Player1"],
|
||||||
|
scoredRunners: [],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle a double correctly', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: ["Player1", "Player2", "Player3"], currentBatter: "Batter"}
|
||||||
|
const advancements = [2, 2, 2, 1];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: [null, "Batter" , "Player1"],
|
||||||
|
scoredRunners: ["Player3", "Player2"],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle a triple correctly', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: ["Player1", "Player2", "Player3"], currentBatter: "Batter"}
|
||||||
|
const advancements = [3, 3, 2, 1];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: [null, null, "Batter"],
|
||||||
|
scoredRunners: ["Player3", "Player2", "Player1"],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle a home run correctly', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: ["Player1", "Player2", "Player3"], currentBatter: "Batter"}
|
||||||
|
const advancements = [4, 3, 2, 1];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: [null, null, null],
|
||||||
|
scoredRunners: ["Player3", "Player2", "Player1", "Batter"],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle no runners correctly', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: [null, null, null, null], currentBatter: "Batter"}
|
||||||
|
const advancements = [0, 0, 0, 0];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: [null, null, null],
|
||||||
|
scoredRunners: [],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle fully loaded bases with a walk correctly', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: ["Player1", "Player2", "Player3"], currentBatter: "Batter"}
|
||||||
|
const advancements = [1, 0, 0, 0];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: ["Batter", "Player1", "Player2"],
|
||||||
|
scoredRunners: ["Player3"],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle single with runner on second base correctly', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: [null, "Player1", null], currentBatter: "Batter"}
|
||||||
|
const advancements = [1, 1, 1, 1];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: ["Batter", null, "Player1"],
|
||||||
|
scoredRunners: [],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle double with runner on second base correctly', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: [null, "Player1", null], currentBatter: "Batter"}
|
||||||
|
const advancements = [2, 2, 2, 1];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: [null, "Batter", null],
|
||||||
|
scoredRunners: ["Player1"],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
it('should handle walk with runner on third base correctly', () => {
|
||||||
|
const batter = "Batter";
|
||||||
|
const prevGameState = {bases: [null, null, "Player1"], currentBatter: "Batter"}
|
||||||
|
const advancements = [1, 0, 0, 0];
|
||||||
|
const {newState, scoredRunners} = advanceRunners(prevGameState, advancements);
|
||||||
|
const expected = {
|
||||||
|
newBases: ["Batter", null, "Player1"],
|
||||||
|
scoredRunners: [],
|
||||||
|
};
|
||||||
|
expect(newState.bases).toEqual(expected.newBases)
|
||||||
|
expect(scoredRunners).toEqual(scoredRunners);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,7 +13,7 @@ function Inning({inning, isTopHalf}){
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function GameStateDisplay({inning, bases, isTopHalf, outs, balls, strikes, awayScore, homeScore, isFinal }){
|
function GameStateDisplay({inning, bases, isTopHalf, outs, count, awayScore, homeScore, isFinal }){
|
||||||
return (
|
return (
|
||||||
<div className="gameState">
|
<div className="gameState">
|
||||||
<header>Scoreboard</header>
|
<header>Scoreboard</header>
|
||||||
@@ -21,7 +21,7 @@ function GameStateDisplay({inning, bases, isTopHalf, outs, balls, strikes, away
|
|||||||
<div>
|
<div>
|
||||||
<Row>
|
<Row>
|
||||||
<Col><Inning inning={inning} isTopHalf={isTopHalf}></Inning></Col>
|
<Col><Inning inning={inning} isTopHalf={isTopHalf}></Inning></Col>
|
||||||
<Col>{balls}-{strikes}</Col>
|
<Col>{count.balls}-{count.strikes}</Col>
|
||||||
<Col>{outs} outs</Col>
|
<Col>{outs} outs</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<FontAwesomeIcon icon={fa1} className={`base ${bases[0] ? "occupied" : ""}`}></FontAwesomeIcon>
|
<FontAwesomeIcon icon={fa1} className={`base ${bases[0] ? "occupied" : ""}`}></FontAwesomeIcon>
|
||||||
|
|||||||
Reference in New Issue
Block a user