Implemented a snake game with redux

This commit is contained in:
2019-08-22 23:59:32 +02:00
commit fe7fae2c19
31 changed files with 1216 additions and 0 deletions

181
src/redux/snake.js Normal file
View File

@@ -0,0 +1,181 @@
const START_SNAKE = "[Snake] START_SNAKE";
const STOP_SNAKE = "[Snake] STOP_SNAKE";
const PAUSE_SNAKE = "[Snake] PAUSE_SNAKE";
const RESET_SNAKE = "[Snake] RESET_SNAKE";
const NEXT_FRAME_SNAKE = "[Snake] NEXT_FRAME_SNAKE";
const UPDATE_FRAME_SNAKE = "[Snake] UPDATE_FRAME_SNAKE";
const KEY_PRESSED_SNAKE = "[Snake] KEY_PRESSED_SNAKE";
export const startSnake = () => ({ type: START_SNAKE });
export const stopSnake = () => ({ type: STOP_SNAKE });
export const pauseSnake = () => ({ type: PAUSE_SNAKE });
export const resetSnake = () => ({ type: RESET_SNAKE });
export const nextFrameSnake = () => ({ type: NEXT_FRAME_SNAKE });
export const updateFrameSnake = () => ({ type: UPDATE_FRAME_SNAKE });
export const keyPressedSnake = key => ({ type: KEY_PRESSED_SNAKE, key });
export const keys = {
UP: "ArrowUp",
RIGHT: "ArrowRight",
DOWN: "ArrowDown",
LEFT: "ArrowLeft",
INCREASE: "+",
DECREASE: "-"
};
export const size = 16;
const randomPosition = size => [Math.round(Math.random() * (size - 1)), Math.round(Math.random() * (size - 1))];
const comparePositions = (pos1, pos2) => pos1[0] === pos2[0] && pos1[1] === pos2[1];
const initialState = {
grid: [[]],
size,
started: false,
paused: false,
vX: 1,
vY: 0,
snake: [[0, 0], [1, 0], [2, 0]],
apple: randomPosition(size),
gameId: 0,
fps: 5
};
const createGrid = size => {
const rows = [];
for (let y = 0; y < size; y += 1) {
const columns = [];
for (let x = 0; x < size; x += 1) {
if (Math.random() > 9) {
columns[x] = "x";
} else {
columns[x] = " ";
}
}
rows[y] = columns;
}
return rows;
};
const markCell = (grid, [x, y], mark) => {
grid[y][x] = mark;
return grid;
};
const moveSnakePart = ([x, y], vX, vY, size) => {
let newX = x + vX;
let newY = y + vY;
if (newX > size - 1) {
newX = 0;
}
if (newX < 0) {
newX = size - 1;
}
if (newY > size - 1) {
newY = 0;
}
if (newY < 0) {
newY = size - 1;
}
return [newX, newY];
};
export const reducers = (state = initialState, action) => {
if (action.type === STOP_SNAKE) {
return { ...state, started: false };
}
if (action.type === PAUSE_SNAKE) {
return { ...state, paused: !state.paused };
}
if (action.type === RESET_SNAKE) {
const grid = createGrid(state.size);
const { snake, apple } = initialState;
markCell(grid, apple, "a");
snake.forEach(p => markCell(grid, p, "s"));
return { ...initialState, started: true, paused: false, grid, snake, gameId: state.gameId + 1 };
}
if (action.type === KEY_PRESSED_SNAKE) {
if (action.key === keys.UP) {
return { ...state, vX: 0, vY: -1 };
}
if (action.key === keys.DOWN) {
return { ...state, vX: 0, vY: 1 };
}
if (action.key === keys.LEFT) {
return { ...state, vX: -1, vY: 0 };
}
if (action.key === keys.RIGHT) {
return { ...state, vX: 1, vY: 0 };
}
if (action.key === keys.INCREASE) {
return { ...state, fps: state.fps + 1 };
}
if (action.key === keys.DECREASE) {
return { ...state, fps: state.fps - 1 };
}
}
if (action.type === UPDATE_FRAME_SNAKE) {
const { snake, vX, vY, size } = state;
let { apple, started, died } = state;
const grid = createGrid(size);
const newSnake = [...snake];
const nextPosition = moveSnakePart(newSnake[newSnake.length - 1], vX, vY, size);
if (comparePositions(nextPosition, apple)) {
apple = randomPosition(size);
// eslint-disable-next-line no-loop-func
while (newSnake.filter(p => comparePositions(p, apple)).length) {
apple = randomPosition(size);
}
} else {
newSnake.shift();
}
if (snake.filter(p => comparePositions(p, nextPosition)).length) {
started = false;
died = true;
}
markCell(grid, apple, "a");
newSnake.push(nextPosition);
newSnake.forEach(p => markCell(grid, p, "s"));
return { ...state, grid, vX, vY, snake: newSnake, apple, started, died };
}
return state;
};
export const middleware = ({ dispatch, getState }) => next => action => {
next(action);
if (action.type === START_SNAKE) {
dispatch(resetSnake());
animate(1, () => dispatch(nextFrameSnake()));
}
if (action.type === NEXT_FRAME_SNAKE) {
const {
snakeGame: { started, paused, gameId, fps }
} = getState();
!paused && dispatch(updateFrameSnake());
started &&
animate(fps, () => {
if (gameId !== getState().snakeGame.gameId) {
return;
}
dispatch(nextFrameSnake());
});
}
};
const animate = (fps, callback) => {
const now = Date.now();
const loop = () =>
requestAnimationFrame(() => {
if (Date.now() - now > 1000 / fps) {
return callback();
}
return loop();
});
return loop();
};