Created
May 11, 2023 18:11
-
-
Save azz0r/6e1af83f0432a8363ffa6964d9f86581 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useReducer, useEffect } from 'react'; | |
const userReducer = (state, action) => { | |
switch (action.type) { | |
case 'MOVE_UP': | |
return { ...state, position: [state.position[0] - 1, state.position[1]] }; | |
case 'MOVE_DOWN': | |
return { ...state, position: [state.position[0] + 1, state.position[1]] }; | |
case 'MOVE_LEFT': | |
return { ...state, position: [state.position[0], state.position[1] - 1] }; | |
case 'MOVE_RIGHT': | |
return { ...state, position: [state.position[0], state.position[1] + 1] }; | |
case 'CLAIM_AREA': | |
return { ...state, claimedArea: true }; | |
default: | |
return state; | |
} | |
}; | |
const moveEnemy = (enemy) => { | |
const { position, direction } = enemy; | |
let [row, col] = position; | |
// Update the enemy's position based on its direction | |
switch (direction) { | |
case 'UP': | |
row--; | |
break; | |
case 'DOWN': | |
row++; | |
break; | |
case 'LEFT': | |
col--; | |
break; | |
case 'RIGHT': | |
col++; | |
break; | |
default: | |
break; | |
} | |
return { ...enemy, position: [row, col] }; | |
}; | |
const enemyReducer = (state, action) => { | |
switch (action.type) { | |
case 'ADD_ENEMY': | |
return [...state, action.payload]; | |
case 'MOVE_ENEMIES': | |
return state.map((enemy) => moveEnemy(enemy)); | |
case 'FREEZE_ENEMY': | |
return state.map((enemy) => | |
enemy.position.toString() === action.payload.toString() | |
? { ...enemy, frozen: true } | |
: enemy | |
); | |
case 'UNFREEZE_ENEMY': | |
return state.map((enemy) => | |
enemy.position.toString() === action.payload.toString() | |
? { ...enemy, frozen: false } | |
: enemy | |
); | |
default: | |
return state; | |
} | |
}; | |
const gameReducer = (state, action) => { | |
switch (action.type) { | |
case 'SET_LIVES': | |
return { ...state, lives: action.payload }; | |
case 'DECREASE_LIVES': | |
return { ...state, lives: state.lives - 1 }; | |
case 'SET_PAUSED': | |
return { ...state, paused: action.payload }; | |
case 'SET_STARTED': | |
return { ...state, started: action.payload }; | |
case 'SET_LEVEL': | |
return { ...state, level: action.payload }; | |
case 'SET_TARGET_PERCENTAGE': | |
return { ...state, targetPercentage: action.payload }; | |
case 'SET_SCORE': | |
return { ...state, score: action.payload }; | |
case 'INCREMENT_SCORE': | |
return { ...state, score: state.score + action.payload }; | |
case 'SET_TIMER': | |
return { ...state, timer: action.payload }; | |
case 'DECREASE_TIMER': | |
return { ...state, timer: state.timer - 1 }; | |
case 'SET_GAME_OVER': | |
return { ...state, gameOver: true }; | |
default: | |
return state; | |
} | |
}; | |
const Board = ({ rows, columns, markerPosition, filledAreas }) => { | |
const gridStyle = { | |
display: 'grid', | |
gridTemplateColumns: `repeat(${columns}, 1fr)`, | |
gap: '1px', | |
backgroundColor: 'black', | |
padding: '10px', | |
}; | |
const cellStyle = { | |
width: '20px', | |
height: '20px', | |
backgroundColor: 'white', | |
}; | |
const filledCellStyle = { | |
backgroundColor: 'blue', | |
}; | |
const isCellFilled = (row, col) => { | |
return filledAreas.some((area) => area?.[row]?.[col]); | |
}; | |
return ( | |
<div style={gridStyle}> | |
{Array.from({ length: rows }, (_, row) => | |
Array.from({ length: columns }, (_, col) => ( | |
<div | |
key={`${row}-${col}`} | |
style={{ | |
...cellStyle, | |
...(isCellFilled(row, col) && filledCellStyle), | |
}} | |
> | |
{markerPosition[0] === row && markerPosition[1] === col && ( | |
<div | |
style={{ | |
width: '10px', | |
height: '10px', | |
borderRadius: '50%', | |
backgroundColor: 'red', | |
margin: '5px', | |
}} | |
/> | |
)} | |
</div> | |
)) | |
)} | |
</div> | |
); | |
}; | |
const calculatePercentageFilled = (filledAreas, totalCells) => { | |
const filledCells = filledAreas.flat().filter((cell) => cell).length; | |
return (filledCells / totalCells) * 100; | |
}; | |
const GameContainer = () => { | |
const rows = 10; // Number of rows in the game board grid | |
const columns = 10; // Number of columns in the game board grid | |
const maxLevels = 5; // Maximum number of levels in the game | |
// Initial state for user position | |
const initialUserState = { | |
position: [0, 0], | |
claimedArea: false, | |
}; | |
// Initial state for enemy positions | |
const initialEnemyState = [ | |
{ position: [2, 2], direction: 'DOWN', frozen: false }, | |
{ position: [7, 7], direction: 'UP', frozen: false }, | |
]; | |
// Initial state for game logic | |
const initialGameState = { | |
lives: 3, | |
paused: false, | |
started: false, | |
level: 1, | |
targetPercentage: 50, | |
score: 0, | |
timer: 10, | |
gameOver: false, | |
}; | |
const [userState, userDispatch] = useReducer(userReducer, initialUserState); | |
const [enemyState, enemyDispatch] = useReducer(enemyReducer, initialEnemyState); | |
const [gameState, gameDispatch] = useReducer(gameReducer, initialGameState); | |
const onStart = () => gameDispatch({ type: 'SET_STARTED', payload: true }); | |
const onResume = () => gameDispatch({ type: 'SET_PAUSED', payload: false }); | |
useEffect(() => { | |
const handleKeyDown = (event) => { | |
if (!gameState.paused && gameState.started) { | |
switch (event.key) { | |
case 'w': | |
userDispatch({ type: 'MOVE_UP' }); | |
break; | |
case 's': | |
userDispatch({ type: 'MOVE_DOWN' }); | |
break; | |
case 'a': | |
userDispatch({ type: 'MOVE_LEFT' }); | |
break; | |
case 'd': | |
userDispatch({ type: 'MOVE_RIGHT' }); | |
break; | |
default: | |
break; | |
} | |
} | |
}; | |
document.addEventListener('keydown', handleKeyDown); | |
return () => { | |
document.removeEventListener('keydown', handleKeyDown); | |
}; | |
}, [gameState.paused, gameState.started, userDispatch]); | |
useEffect(() => { | |
const intervalId = setInterval(() => { | |
if (!gameState.paused && gameState.started && !gameState.gameOver) { | |
enemyDispatch({ type: 'MOVE_ENEMIES' }); | |
const playerPosition = userState.position; | |
const enemyPositions = enemyState.map((enemy) => enemy.position); | |
if (enemyPositions.some((pos) => pos.toString() === playerPosition.toString())) { | |
gameDispatch({ type: 'DECREASE_LIVES' }); | |
if (gameState.lives === 0) { | |
gameDispatch({ type: 'SET_GAME_OVER' }); | |
} | |
} | |
if (userState.claimedArea) { | |
const filledAreas = [...enemyState, { position: userState.position, frozen: false }]; | |
const percentageFilled = calculatePercentageFilled(filledAreas, rows * columns); | |
gameDispatch({ type: 'INCREMENT_SCORE', payload: percentageFilled }); | |
if (percentageFilled >= gameState.targetPercentage) { | |
const nextLevel = gameState.level + 1; | |
const nextTargetPercentage = gameState.targetPercentage + 10; | |
const nextTimer = gameState.timer - 1; | |
gameDispatch({ type: 'SET_LEVEL', payload: nextLevel }); | |
gameDispatch({ type: 'SET_TARGET_PERCENTAGE', payload: nextTargetPercentage }); | |
gameDispatch({ type: 'SET_TIMER', payload: nextTimer }); | |
if (nextLevel > maxLevels) { | |
gameDispatch({ type: 'SET_GAME_OVER' }); | |
} else { | |
// Reset game state and proceed to the next level | |
userDispatch(initialUserState); | |
enemyDispatch(initialEnemyState); | |
} | |
} else { | |
userDispatch({ type: 'CLAIM_AREA' }); | |
} | |
} | |
} | |
}, gameState.timer * 1000); | |
return () => { | |
clearInterval(intervalId); | |
}; | |
}, [ | |
gameState.paused, | |
gameState.started, | |
gameState.gameOver, | |
gameState.lives, | |
gameState.targetPercentage, | |
gameState.level, | |
gameState.timer, | |
userState.position, | |
userState.claimedArea, | |
enemyState, | |
userDispatch, | |
enemyDispatch, | |
gameDispatch, | |
]); | |
// Add your game logic here, including handling user input and dispatching actions | |
if (gameState.lives === 0) { | |
return <div>Game Over</div>; | |
} | |
if (gameState.paused) { | |
return ( | |
<> | |
<div>Paused</div> | |
<button onClick={onResume}>Resume</button> | |
</> | |
) | |
} | |
if (!gameState.started) { | |
return ( | |
<> | |
<div>Press Start to begin</div> | |
<button onClick={onStart}>Start</button> | |
</> | |
) | |
} | |
return ( | |
<div> | |
<Board rows={10} columns={10} markerPosition={userState.position} filledAreas={enemyState} /> | |
</div> | |
); | |
}; | |
export default GameContainer; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment