我正在重构我的Simon游戏应用程序以使用Redux钩子,但是,我的Redux存储中的userPattern数组的状态不会立即更新;在用户选择了应该复制的图案中的其他颜色后,它会更新。例如,如果用户处于级别1,则userPattern不会更新,直到用户选择了两种颜色。 userPattern的console.log有助于我确认userPattern的状态在选择第二种颜色之前不会更新。
下面,我将发布我的代码。请注意,当Button.js和App.js是类组件(Buttons.js始终是功能组件)时,该程序可以工作。提前谢谢你的帮助。 (如果您发现我发布的与此类似的问题,请忽略它。我无意中将其关闭了。)
App.js
import React, {useState, useEffect} from 'react';
import './App.css';
import './styles/styles.scss';
import red from './sounds/red.mp3'
import blue from './sounds/blue.mp3'
import green from './sounds/green.mp3'
import yellow from './sounds/yellow.mp3'
import wrong from './sounds/wrong.mp3'
import Buttons from './Buttons/Buttons'
import { useDispatch, useSelector, shallowEqual} from 'react-redux';
import { toggleGameOver, updateLevel, turnOnReadyForUserInput, turnOffReadyForUserInput, toggleSrtictMode, resetSimonGame, updateGamePattern, toggleGameStarted, setActiveStyle, emptyUserPattern, updateLastColor, turnOnUserIsWrong, turnOffUserIsWrong, setPlayerLevel } from './actions/actions'
var intervalRepeatSequence;
const App = () => {
const [colors, setColors] = useState(["red","blue","green","yellow"]);
const [sounds, setSounds] = useState({
red: new Audio(red),
blue: new Audio(blue),
green: new Audio(green),
yellow: new Audio(yellow),
wrong: new Audio(wrong)
})
const dispatch = useDispatch();
const readyForUserInput = useSelector(state => state.readyForUserInput);
const level = useSelector(state => state.level);
const gameStarted = useSelector(state => state.gameStarted);
const gamePattern = useSelector(state => state.gamePattern);
const userPattern = useSelector(state => state.userPattern);
const userIsWrong = useSelector(state => state.userIsWrong);
const strictMode = useSelector(state => state.strictMode);
const strictRestart = useSelector(state => state.strictRestart);
const gameOver = useSelector(state => state.gameOver);
const handleNewSequence = () => {
let randomColor = colors[Math.floor(Math.random() * 4)];
console.log("gameStarted " + gameStarted)
const currentLevel = gameStarted ? level : level + 1;
//Adding new color to game pattern
dispatch(updateGamePattern([...gamePattern, randomColor]));
dispatch(updateLastColor(randomColor));
dispatch(setPlayerLevel({level: currentLevel}));
if (!gameStarted){
dispatch(toggleGameStarted());
}
fadeInFadeOut(randomColor);
//used for prevent user from picking colors while the sequence is being shown to them
setTimeout(() => {
dispatch(turnOnReadyForUserInput())
}, 500)
}
//when the user picks an incorrect color, the background will change to red until gameOver is false again
useEffect(() => {
if (gameOver){
document.body.style.background = "red";
} else {
document.body.style.background = "linear-gradient(to right, #FC466B , #3F5EFB)"
}
},[gameOver])
//handles the fade in and fade out animations when the user is being shown the sequence they must replicate
const fadeInFadeOut = (color) => {
dispatch(setActiveStyle('fadeOut '));
//after half a second the fade in animation will occur
setTimeout(() => {
dispatch(setActiveStyle('fadeIn '));
dispatch(updateLastColor())
}, 500)
sounds[color].play();
}
const handleInput = () => {
let initialCheck = setTimeout(() => {
let index = userPattern.length - 1;
//compares elements in regular mode
if ((userPattern[index] !== gamePattern[index]) && !strictMode) {
dispatch(turnOffReadyForUserInput());
verifyUserMoves();
}
// compares elements in both strict mode and regular mode when the user has input all the colors; basically used to check if the user
// got the last in the pattern correct, since they've gotten the rest of the pattern correct
console.log('userPattern ' + userPattern)
if ((userPattern.length === gamePattern.length) && !strictRestart) {
//disables buttons when user finishes their turn
console.log("In handleInput")
dispatch(turnOffReadyForUserInput());
verifyUserMoves();
}
}, 100)
// used to validate patterns when strict mode is turned on
setTimeout(() => {
if (strictMode){
let index = userPattern.length - 1;
if (userPattern[index] !== gamePattern[index])
{
// Prevents the initial check from occuring because at this point we know the user has already input the incorrect pattern
clearTimeout(initialCheck);
dispatch(turnOffReadyForUserInput());
verifyUserMoves();
}
}
},50)
}
// if regular mode is on and the user is incorrect or correct, the sequence will just be replayed;
// the userIsWrong property will be used later in repeatSequence to determine whether a new color should
// be added to the sequence or not
const verifyUserMoves = () => {
if (userPattern.join() === gamePattern.join()){
dispatch(updateLevel());
console.log('updated level')
repeatSequence();
} else {
if (strictMode){
wrongAnswer()
setTimeout(() => {
resetGame();
},500)
} else {
wrongAnswer();
repeatSequence();
}
}
}
const wrongAnswer = () => {
dispatch(toggleGameOver());
sounds["wrong"].play();
//a delay is used to so that the header will return back to "Click me to begin game" or the current level of the game
//and to return the background to normal
setTimeout(() => {
dispatch(toggleGameOver());
dispatch(turnOnUserIsWrong());
},500)
}
const repeatSequence = () => {
dispatch(emptyUserPattern())
var index = -1;
// this will iterate through the colors and use the fadeInFadeOut function to apply the animations
intervalRepeatSequence = setInterval(() => {
index++;
dispatch(updateLastColor());
if (index <= gamePattern.length -1){
let currentColor = gamePattern[index];
setTimeout(()=> {
dispatch(updateLastColor(currentColor));
fadeInFadeOut(currentColor);
},100)
} else {
//makes sure the current iteration won't coincide with the next
clearInterval(intervalRepeatSequence);
//if the user isn't wrong a new color is added to the game pattern
if(!userIsWrong){
setTimeout(()=>{
handleNewSequence();
},100)
}
if (userIsWrong) {
dispatch(turnOnReadyForUserInput());
dispatch(turnOffUserIsWrong());
}
}
},1000)
}
// resets the game
const resetGame = () => {
clearInterval(intervalRepeatSequence);
dispatch(resetSimonGame());
}
// turns on strict mode
const handleStrictToggle = () => {
resetGame();
dispatch(toggleSrtictMode());
}
return(
<div
className={!readyForUserInput && level > 0 ? "pointer-events-disabled" : null}>
<h1
className="header"
onClick={level === 0 ? handleNewSequence : null}>{!gameStarted && level === 0 ? "Click me to begin game" : gameStarted && gameOver ? "Wrong. Please try again" : level > 0 ? "Level: " + level : null}</h1>
<p
className={!strictMode ? "subheader" : "subheaderClicked"}
onClick={handleStrictToggle}>Strict Mode</p>
<p className={"subheader"} onClick={resetGame}>Reset</p>
<div>
<div>
<Buttons colors={colors} sounds={sounds} handleInput={handleInput}/>
</div>
</div>
</div>
)
}
export default App;
Buttons.js
import React from 'react';
import Button from '../Button'
// Just a presentational component which contains the buttons
const Buttons = (props) => {
return (
<div>
<div>
{props.colors.slice(0,2).map(color => {
return(
<Button id={color} key={color} sound={props.sounds[color]} handleInput={props.handleInput}/>
)
})}
</div>
<div>
{props.colors.slice(2,4).map(color => {
return(
<Button id={color} key={color} sound={props.sounds[color]} handleInput={props.handleInput}/>
)
})}
</div>
</div>
)
}
export default Buttons;
Button.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { togglePressed } from './actions/actions'
import { updateUserPattern } from './actions/actions'
const Button = ({id, sound, handleInput}) => {
const dispatch = useDispatch();
const lastColor = useSelector(state => state.lastColor);
const activeStyle = useSelector(state => state.activeStyle);
const readyForUserInput = useSelector(state => state.readyForUserInput);
const pressed = useSelector(state => state.pressed)
const userPattern = useSelector(state => state.userPattern)
const chooseColor = () => {
if (readyForUserInput){
sound.pause();
sound.currentTime = 0;
dispatch(updateUserPattern(id))
console.log(`userPattern ` + userPattern)
dispatch(togglePressed(id))
setTimeout(() => {
dispatch(togglePressed())
},100)
sound.play();
handleInput();
}
}
return (
<div className={lastColor === id ?
activeStyle + id + " button pointer-events-disabled" :
!readyForUserInput ? id + " button pointer-events-disabled" :
pressed === id ?
// When you click on the button it will light up and handleInput will be called in App.js and determine how the input should be interpretted
id + " button pressed" : id + " button"} onClick={chooseColor}></div>
)
}
export default Button;
reducer.js
const simonReduceDefaultState = {
gamePattern: [],
//the pattern that the user inputs
userPattern: [],
//used to indicate the current color in the sequence; used for fade in and fade out animation
lastColor: "",
//keeps track of the current level
level: 0,
//indicates whether game is over or not
gameOver: false,
//indicates if game has started
gameStarted: false,
//indicates when the user has input the wrong pattern
userIsWrong: false,
//indicates when the game will allow the user to pick colors for their pattern
readyForUserInput: false,
//used to track whether strict mode (if the user picks a wrong color the game resets) is on or not
strictMode: false,
//used to see whether if a strict restart has occured; allows us to figure out whether the users pattern is incorrect when they
//get past level 1
strictRestart: false,
//used to add light-up border animation
pressed: '',
//used to apply fade in, fade out animation
activeStyle: '',
}
export default (state = simonReduceDefaultState, action) => {
switch (action.type) {
case 'SET_ACTIVE_STYLE':
return {
...state,
activeStyle: action.style
}
case 'UPDATE_LAST_COLOR':
return {
...state,
lastColor: action.color
}
case 'UPDATE_USER_PATTERN':
return {
...state,
userPattern: [...state.userPattern, action.id]
}
case 'UPDATE_GAME_PATTERN':
return {
...state,
gamePattern: [...action.newGamePattern]
}
case 'TOGGLE_PRESSED':
console.log(action.color)
return {
...state,
pressed: action.color
}
case 'TURN_ON_READY_FOR_USER_INPUT':
return {
...state,
readyForUserInput: true
}
case 'TURN_OFF_READY_FOR_USER_INPUT':
return {
...state,
readyForUserInput: false
}
case 'RESET_GAME':
return {
...state,
gamePattern: [],
userPattern: [],
lastColor: "",
level: 0,
gameStarted: false,
userIsWrong: false,
readyForUserInput: false,
activeStyle: '',
strictRestart: false
}
case 'UPDATE_LEVEL':
return {
...state,
level: state.level + action.level
}
case 'TURN_OFF_USER_IS_WRONG':
return{
...state,
userIsWrong: false
}
case 'TURN_ON_USER_IS_WRONG':
return{
...state,
userIsWrong: true
}
case 'TOGGLE_STRICT_MODE':
return {
...state,
strictMode: !state.strictMode
}
case 'TOGGLE_GAME_STARTED':
return {
...state,
gameStarted: !state.gameStarted
}
case 'TOGGLE_GAME_OVER':
return {
...state,
gameOver: !state.gameOver
}
case 'EMPTY_USER_PATTERN':
return {
...state,
userPattern: []
}
case 'SET_PLAYER_LEVEL':
return{
...state,
level: action.level
}
default:
return {
...state
};
}
}
操作
export const setActiveStyle = (style = '') => ({
type: 'SET_ACTIVE_STYLE',
style
})
export const updateLastColor = (color = '') => ({
type:'UPDATE_LAST_COLOR',
color
})
export const updateUserPattern = (id = '') => ({
type: 'UPDATE_USER_PATTERN',
id
})
export const updateGamePattern = (newGamePattern =[]) => ({
type: 'UPDATE_GAME_PATTERN',
newGamePattern
})
export const togglePressed = (color = '') => ({
type: 'TOGGLE_PRESSED',
color
})
export const turnOnReadyForUserInput = () => ({
type: 'TURN_ON_READY_FOR_USER_INPUT'
})
export const turnOffReadyForUserInput = () => ({
type: 'TURN_OFF_READY_FOR_USER_INPUT'
})
export const resetSimonGame = () => ({
type: 'RESET_GAME'
})
export const updateLevel = ({level = 1} = {}) => ({
type: 'UPDATE_LEVEL',
level
})
export const setPlayerLevel = ({level} = {}) => ({
type: 'SET_PLAYER_LEVEL',
level
})
export const toggleStrictRestart = () => ({
type: "TOGGLE_STRICT_RESTART"
})
export const toggleSrtictMode = () => ({
type: 'TOGGLE_STRICT_MODE'
})
export const toggleGameStarted = () => ({
type: 'TOGGLE_GAME_STARTED'
})
export const toggleGameOver = () => ({
type: 'TOGGLE_GAME_OVER'
})
export const emptyUserPattern = () => ({
type: 'EMPTY_USER_PATTERN'
})
export const turnOffUserIsWrong = () => ({
type: 'TURN_OFF_USER_IS_WRONG'
})
export const turnOnUserIsWrong = () => ({
type: 'TURN_ON_USER_IS_WRONG'
})