Fixed league/practice menus

This commit is contained in:
corey@blaishome.online
2025-02-16 16:07:49 -05:00
parent 0d05370cb9
commit a2ff6c9449
5 changed files with 266 additions and 81 deletions

View File

@@ -1,17 +1,77 @@
// File: src/App.js
import React from 'react';
import { ScoreProvider } from './context/ScoreContext';
import AppRouter from './AppRouter';
import { ThemeProvider } from './context/ThemeContext';
import React, { useState, useMemo, useEffect } from 'react';
import { CssBaseline, Container, IconButton } from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles'; // <-- Import createTheme
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { ScoreProvider } from './context/ScoreContext'; // <-- Import ScoreProvider
import MainMenu from './components/MainMenu';
import GameSetup from './components/GameSetup';
import ScoreTracker from './components/ScoreTracker';
import GameSummary from './components/GameSummary';
import { Brightness7, Brightness4 } from '@mui/icons-material';
function App() {
const [mode, setMode] = useState(() => {
try {
const savedMode = localStorage.getItem('themeMode');
return savedMode || 'light';
} catch (e) {
return 'light';
}
});
// Save theme preference to localStorage
useEffect(() => {
localStorage.setItem('themeMode', mode);
}, [mode]);
const theme = useMemo(
() =>
createTheme({
palette: {
mode,
},
}),
[mode],
);
const toggleTheme = () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
};
return (
<ThemeProvider>
<ThemeProvider theme={theme}>
<ScoreProvider>
<AppRouter />
<CssBaseline />
<Container
maxWidth="lg"
sx={{
minHeight: '100vh',
bgcolor: 'background.default',
color: 'text.primary',
pb: 4
}}
>
<IconButton
onClick={toggleTheme}
color="inherit"
sx={{ position: 'absolute', top: 16, right: 16 }}
>
{mode === 'dark' ? <Brightness7 /> : <Brightness4 />}
</IconButton>
<Router>
<Routes>
<Route path="/" element={<MainMenu />} /> {/* MainMenu as the default page */}
<Route path="/setup" element={<GameSetup />} />
<Route path="/score-tracker" element={<ScoreTracker />} />
<Route path="/summary" element={<GameSummary />} />
</Routes>
</Router>
</Container>
</ScoreProvider>
</ThemeProvider>
);
}
export default App;

View File

@@ -1,4 +1,3 @@
// src/components/GameSetup.js
import React from 'react';
import {
Button,
@@ -15,8 +14,8 @@ const GameSetup = ({ onGameStart }) => {
const startGame = (gameType) => {
dispatch({
type: ACTIONS.START_NEW_ROUND,
payload: { gameType }
type: ACTIONS.START_GAME,
payload: { gameType, isLeague: false }
});
onGameStart();
};

View File

@@ -7,16 +7,18 @@ const MainMenu = () => {
const navigate = useNavigate();
const { dispatch } = useScore();
const handleGameStart = (gameType, isLeague) => {
// Start the game and navigate directly to the score tracker
const handleGameStart = (gameType, isLeague) => {
dispatch({
type: ACTIONS.START_GAME,
type: ACTIONS.START_NEW_ROUND,
payload: {
gameType: gameType,
isLeague: isLeague
}
gameType,
isLeague // Pass the league or practice mode
},
});
navigate('/game');
};
navigate('/score-tracker'); // Jump to score tracker
};
return (
<Box sx={{
@@ -40,7 +42,7 @@ const MainMenu = () => {
<Button
variant="contained"
fullWidth
onClick={() => handleGameStart('450', true)}
onClick={() => handleGameStart('450', true)} // Start the 450 game as a league
sx={{ height: 60 }}
>
450 Round
@@ -50,7 +52,7 @@ const MainMenu = () => {
<Button
variant="contained"
fullWidth
onClick={() => handleGameStart('300', true)}
onClick={() => handleGameStart('300', true)} // Start the 300 game as a league
sx={{ height: 60 }}
>
300 Round
@@ -69,7 +71,7 @@ const MainMenu = () => {
<Button
variant="outlined"
fullWidth
onClick={() => handleGameStart('450', false)}
onClick={() => handleGameStart('450', false)} // Start the 450 game for practice
sx={{ height: 60 }}
>
450 Round
@@ -79,7 +81,7 @@ const MainMenu = () => {
<Button
variant="outlined"
fullWidth
onClick={() => handleGameStart('300', false)}
onClick={() => handleGameStart('300', false)} // Start the 300 game for practice
sx={{ height: 60 }}
>
300 Round
@@ -94,7 +96,7 @@ const MainMenu = () => {
variant="contained"
color="secondary"
fullWidth
onClick={() => navigate('/history')}
onClick={() => navigate('/history')} // Ensure this route exists in your app
sx={{ height: 60 }}
>
View League History
@@ -105,3 +107,4 @@ const MainMenu = () => {
};
export default MainMenu;

View File

@@ -6,20 +6,15 @@ import { useNavigate } from 'react-router-dom';
const ScoreTracker = () => {
const { state, dispatch } = useScore();
const navigate = useNavigate();
const gameType = state.currentGame.gameType;
const isLeague = state.currentGame.isLeague; // Check if it's a league game
const maxArrowsPerRound = gameType === '450' ? 3 : 5;
const maxScore = gameType === '450' ? 10 : 5;
const maxRounds = gameType === '450' ? 16 : 12;
const [arrowScores, setArrowScores] = useState(Array(maxArrowsPerRound).fill(''));
const handleMainMenu = () => {
if (window.confirm('Are you sure you want to return to the main menu? Current game progress will be lost.')) {
dispatch({ type: ACTIONS.RESET_GAME });
navigate('/');
}
};
const handleScoreChange = (index, value) => {
const updatedScores = [...arrowScores];
updatedScores[index] = value;
@@ -27,7 +22,6 @@ const ScoreTracker = () => {
};
const handleAddRound = () => {
if (state.currentGame.rounds.length >= maxRounds - 1) { // Check if this is the last round
const valid = arrowScores.slice(0, maxArrowsPerRound).every(score =>
(score >= 0 && score <= maxScore) || score.toUpperCase() === 'X'
);
@@ -45,66 +39,193 @@ const ScoreTracker = () => {
};
});
dispatch({
type: ACTIONS.ADD_ROUND,
payload: {
roundIndex: state.currentGame.rounds.length,
arrows: roundArrows,
},
});
navigate('/summary');
return;
}
// Regular round handling
const valid = arrowScores.slice(0, maxArrowsPerRound).every(score =>
(score >= 0 && score <= maxScore) || score.toUpperCase() === 'X'
);
if (!valid) {
alert(`Please enter valid scores between 0-${maxScore} or X for bullseyes.`);
return;
}
const roundArrows = arrowScores.slice(0, maxArrowsPerRound).map((score) => {
const arrowScore = score.toUpperCase() === 'X' ? maxScore : parseInt(score, 10);
return {
score: arrowScore,
isBullseye: score.toUpperCase() === 'X',
};
});
const roundTotal = roundArrows.reduce((sum, arrow) => sum + arrow.score, 0);
dispatch({
type: ACTIONS.ADD_ROUND,
payload: {
roundIndex: state.currentGame.rounds.length,
arrows: roundArrows,
total: roundTotal
},
});
setArrowScores(Array(maxArrowsPerRound).fill(''));
if (state.currentGame.rounds.length >= maxRounds - 1) {
navigate('/summary');
}
};
const handleMainMenu = () => {
dispatch({ type: ACTIONS.RESET_GAME });
navigate('/');
};
const handleGameStart = (gameType, isLeague) => {
// Dispatch the action to start a new round with the correct game type and isLeague flag
dispatch({
type: ACTIONS.START_NEW_ROUND,
payload: {
gameType, // '450' or '300'
isLeague // true for league games, false for practice games
}
});
navigate('/score-tracker'); // Navigate directly to the score tracker
};
return (
<Box sx={{ position: 'relative', width: '100%', p: 2 }}>
<Grid container spacing={2} justifyContent="center">
{/* Main Menu Button */}
<Box
sx={{
position: 'absolute',
top: 16,
left: 16,
}}
>
<Button
variant="outlined"
onClick={handleMainMenu}
sx={{
position: 'absolute',
left: 16,
top: 16,
}}
>
Main Menu
</Button>
<Grid container spacing={2} justifyContent="center">
{/* ... rest of your JSX ... */}
</Grid>
</Box>
<Grid item xs={12}>
<Typography variant="h5" align="center" gutterBottom>
{gameType} Round - Round {state.currentGame.rounds.length + 1}
</Typography>
<Typography variant="subtitle1" align="center" gutterBottom>
{isLeague ? 'League Game' : 'Practice Game'} {/* Display whether it's a league or practice game */}
</Typography>
</Grid>
{/* Score input section */}
<Grid item xs={12}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
gap: 1,
mb: 2
}}
>
{Array.from({ length: maxArrowsPerRound }).map((_, index) => (
<TextField
key={index}
value={arrowScores[index]}
onChange={(e) => handleScoreChange(index, e.target.value)}
placeholder="0"
size="small"
sx={{
width: '60px',
'& .MuiInputBase-input': {
padding: '8px',
textAlign: 'center'
}
}}
inputProps={{
maxLength: 1,
style: { textAlign: 'center' }
}}
/>
))}
</Box>
</Grid>
{/* Score buttons */}
<Grid item xs={12}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gap: 1,
mb: 2
}}
>
{Array.from({ length: maxScore + 1 }).map((_, i) => (
<Button
key={i}
variant="outlined"
size="small"
sx={{ minWidth: '40px', height: '40px' }}
onClick={() => {
const emptyIndex = arrowScores.findIndex(score => score === '');
if (emptyIndex >= 0 && emptyIndex < maxArrowsPerRound) {
handleScoreChange(emptyIndex, i.toString());
}
}}
>
{i}
</Button>
))}
<Button
variant="outlined"
size="small"
sx={{ minWidth: '40px', height: '40px' }}
onClick={() => {
const emptyIndex = arrowScores.findIndex(score => score === '');
if (emptyIndex >= 0 && emptyIndex < maxArrowsPerRound) {
handleScoreChange(emptyIndex, 'X');
}
}}
>
X
</Button>
</Box>
</Grid>
{/* Control buttons */}
<Grid item xs={12}>
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2 }}>
<Button
variant="contained"
color="primary"
onClick={handleAddRound}
disabled={!arrowScores.slice(0, maxArrowsPerRound).every(score => score !== '')}
>
Add Round
</Button>
<Button
variant="outlined"
onClick={() => setArrowScores(Array(maxArrowsPerRound).fill(''))}
>
Clear
</Button>
</Box>
</Grid>
{/* Scores display */}
<Grid item xs={12}>
<Typography variant="h6" align="center">
Total Score: {state.currentGame.totalScore} |
Bullseyes: {state.currentGame.totalBullseyes}
</Typography>
</Grid>
{/* Round history */}
<Grid item xs={12}>
<Box sx={{ maxHeight: '200px', overflow: 'auto' }}>
{state.currentGame.rounds.length > 0 ? (
state.currentGame.rounds.map((round, roundIndex) => (
<Typography key={roundIndex} variant="body2" align="center">
Round {roundIndex + 1}: {round.arrows.map(arrow => arrow.score).join(', ')}
(Total: {round.total}, Bullseyes: {round.bullseyes})
</Typography>
))
) : (
<Typography variant="body2" align="center">
No rounds played yet.
</Typography>
)}
</Box>
</Grid>
</Grid>
);
};
export default ScoreTracker;

View File

@@ -22,22 +22,24 @@ const initialState = {
const scoreReducer = (state, action) => {
switch (action.type) {
case ACTIONS.START_GAME:
case ACTIONS.START_NEW_ROUND:
return {
...state,
currentGame: {
...initialState.currentGame,
gameType: action.payload.gameType,
isLeague: action.payload.isLeague,
date: new Date().toISOString()
}
rounds: [],
totalScore: 0,
totalBullseyes: 0,
dateStarted: new Date().toISOString(),
},
};
case ACTIONS.ADD_ROUND:
const updatedRounds = [...state.currentGame.rounds];
updatedRounds[action.payload.roundIndex] = {
arrows: action.payload.arrows,
total: action.payload.arrows.reduce((sum, arrow) => sum + arrow.score, 0),
total: action.payload.total,
bullseyes: action.payload.arrows.filter(arrow => arrow.isBullseye).length
};
@@ -51,7 +53,7 @@ const scoreReducer = (state, action) => {
rounds: updatedRounds,
totalScore,
totalBullseyes
}
},
};
case ACTIONS.SAVE_GAME: