Refactored redux code and snake game
This commit is contained in:
@@ -1,46 +1,59 @@
|
||||
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";
|
||||
import Module from "./module.js";
|
||||
import {
|
||||
registerCaller,
|
||||
unregisterCaller,
|
||||
startLoop,
|
||||
stopLoop,
|
||||
subscribeKeyPressed,
|
||||
unsubscribeKeyPressed
|
||||
} from "./game.js";
|
||||
|
||||
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 });
|
||||
const MODULE_NAME = "SNAKE";
|
||||
const DEFAULT_GRID_SIZE = 16;
|
||||
|
||||
const initialState = {
|
||||
grid: createGrid(DEFAULT_GRID_SIZE),
|
||||
size: DEFAULT_GRID_SIZE,
|
||||
paused: false,
|
||||
vX: 1,
|
||||
vY: 0,
|
||||
snake: [],
|
||||
score: 0,
|
||||
previousScore: 0,
|
||||
apple: randomPosition(DEFAULT_GRID_SIZE)
|
||||
};
|
||||
|
||||
/* === Snake Helper Functions ================================================================== */
|
||||
|
||||
export const directions = {
|
||||
UP: "UP",
|
||||
DOWN: "DOWN",
|
||||
LEFT: "LEFT",
|
||||
RIGHT: "RIGHT"
|
||||
};
|
||||
|
||||
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))];
|
||||
h: "h",
|
||||
j: "j",
|
||||
k: "k",
|
||||
l: "l",
|
||||
|
||||
p: "p",
|
||||
s: "s",
|
||||
r: "r"
|
||||
};
|
||||
|
||||
function randomPosition(size) {
|
||||
return [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 => {
|
||||
function createGrid(size) {
|
||||
const rows = [];
|
||||
for (let y = 0; y < size; y += 1) {
|
||||
const columns = [];
|
||||
@@ -54,7 +67,7 @@ const createGrid = size => {
|
||||
rows[y] = columns;
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
}
|
||||
|
||||
const markCell = (grid, [x, y], mark) => {
|
||||
grid[y][x] = mark;
|
||||
@@ -79,103 +92,134 @@ const moveSnakePart = ([x, y], vX, vY, size) => {
|
||||
return [newX, newY];
|
||||
};
|
||||
|
||||
export const reducers = (state = initialState, action) => {
|
||||
if (action.type === STOP_SNAKE) {
|
||||
return { ...state, started: false };
|
||||
}
|
||||
export const module = new Module(MODULE_NAME, initialState);
|
||||
|
||||
if (action.type === PAUSE_SNAKE) {
|
||||
return { ...state, paused: !state.paused };
|
||||
}
|
||||
/* === Actions ================================================================================== */
|
||||
|
||||
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 };
|
||||
}
|
||||
export const [START_SNAKE, startSnake] = module.action("START_SNAKE");
|
||||
export const [RESET_SNAKE, resetSnake] = module.action("RESET_SNAKE", options => ({ ...options }));
|
||||
export const [STOP_SNAKE, stopSnake] = module.action("STOP_SNAKE");
|
||||
export const [PAUSE_SNAKE, pauseSnake] = module.action("PAUSE_SNAKE");
|
||||
export const [UPDATE_FRAME_SNAKE, updateFrameSnake] = module.action("UPDATE_FRAME_SNAKE");
|
||||
export const [CHANGE_DIRECTION, changeDirection] = module.action("CHANGE_DIRECTION", direction => ({ direction }));
|
||||
export const [KEY_PRESSED_SNAKE, keyPressedSnake] = module.action("KEY_PRESSED_SNAKE", key => ({ key }));
|
||||
export const [UPDATE_STATE, updateState] = module.action("UPDATE_STATE", (newState, fullUpdate = false) => ({
|
||||
newState,
|
||||
fullUpdate
|
||||
}));
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
/* === Reducers ================================================================================= */
|
||||
|
||||
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 };
|
||||
module.reducer(UPDATE_STATE, (state, { newState, fullUpdate }) => (fullUpdate ? newState : { ...state, ...newState }));
|
||||
module.reducer(RESET_SNAKE, (state, options) => {
|
||||
const grid = createGrid(state.size);
|
||||
const { apple } = initialState;
|
||||
const snake = options.started ? [[0, 0]] : [];
|
||||
|
||||
markCell(grid, apple, "a");
|
||||
snake.forEach(p => markCell(grid, p, "s"));
|
||||
return { ...state, ...initialState, grid, snake, ...options };
|
||||
});
|
||||
|
||||
module.reducer(CHANGE_DIRECTION, (state, { direction }) => {
|
||||
const { vX, vY } = state;
|
||||
|
||||
if (direction === directions.UP && vY !== 1) {
|
||||
return { ...state, vX: 0, vY: -1 };
|
||||
}
|
||||
if (direction === directions.DOWN && vY !== -1) {
|
||||
return { ...state, vX: 0, vY: 1 };
|
||||
}
|
||||
if (direction === directions.LEFT && vX !== 1) {
|
||||
return { ...state, vX: -1, vY: 0 };
|
||||
}
|
||||
if (direction === directions.RIGHT && vX !== -1) {
|
||||
return { ...state, vX: 1, vY: 0 };
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
});
|
||||
|
||||
export const middleware = ({ dispatch, getState }) => next => action => {
|
||||
next(action);
|
||||
|
||||
if (action.type === START_SNAKE) {
|
||||
dispatch(resetSnake());
|
||||
animate(1, () => dispatch(nextFrameSnake()));
|
||||
module.reducer(UPDATE_FRAME_SNAKE, state => {
|
||||
const { snake, vX, vY, size } = state;
|
||||
let { apple, started, died, previousScore, score } = state;
|
||||
const grid = createGrid(size);
|
||||
const newSnake = [...snake];
|
||||
const nextPosition = moveSnakePart(newSnake[newSnake.length - 1], vX, vY, size);
|
||||
previousScore = score;
|
||||
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 (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());
|
||||
});
|
||||
if (newSnake.filter(p => comparePositions(p, nextPosition)).length) {
|
||||
started = false;
|
||||
died = true;
|
||||
}
|
||||
};
|
||||
newSnake.push(nextPosition);
|
||||
markCell(grid, apple, "a");
|
||||
const brightness = (l, i) => 20 + Math.round(0.8 * ((i + 1) / l) * 100);
|
||||
newSnake.forEach((p, i) => markCell(grid, p, "s" + brightness(newSnake.length, i)));
|
||||
score = newSnake.length;
|
||||
return { ...state, grid, vX, vY, snake: newSnake, apple, started, died, previousScore, score };
|
||||
});
|
||||
|
||||
const animate = (fps, callback) => {
|
||||
const now = Date.now();
|
||||
const loop = () =>
|
||||
requestAnimationFrame(() => {
|
||||
if (Date.now() - now > 1000 / fps) {
|
||||
return callback();
|
||||
}
|
||||
return loop();
|
||||
});
|
||||
return loop();
|
||||
};
|
||||
/* === Middleware =============================================================================== */
|
||||
|
||||
module.after(UPDATE_FRAME_SNAKE, (dispatch, { died }) => {
|
||||
if (died) {
|
||||
dispatch(unregisterCaller(UPDATE_FRAME_SNAKE));
|
||||
dispatch(stopLoop());
|
||||
}
|
||||
});
|
||||
|
||||
module.middleware(START_SNAKE, (dispatch, { started }) => {
|
||||
dispatch(resetSnake({ started: true, died: false }));
|
||||
dispatch(registerCaller(UPDATE_FRAME_SNAKE));
|
||||
dispatch(subscribeKeyPressed(KEY_PRESSED_SNAKE));
|
||||
dispatch(startLoop());
|
||||
});
|
||||
|
||||
module.middleware(STOP_SNAKE, (dispatch, { started }) => {
|
||||
dispatch(unregisterCaller(UPDATE_FRAME_SNAKE));
|
||||
dispatch(unsubscribeKeyPressed(KEY_PRESSED_SNAKE));
|
||||
dispatch(stopLoop());
|
||||
dispatch(resetSnake({ started: false }));
|
||||
});
|
||||
|
||||
module.middleware(PAUSE_SNAKE, (dispatch, { paused }) => {
|
||||
dispatch(updateState({ paused: !paused }));
|
||||
if (paused) {
|
||||
dispatch(registerCaller(UPDATE_FRAME_SNAKE));
|
||||
} else {
|
||||
dispatch(unregisterCaller(UPDATE_FRAME_SNAKE));
|
||||
}
|
||||
});
|
||||
|
||||
module.middleware(KEY_PRESSED_SNAKE, (dispatch, _, { key }) => {
|
||||
if (key === keys.UP || key === keys.k) {
|
||||
dispatch(changeDirection(directions.UP));
|
||||
}
|
||||
if (key === keys.DOWN || key === keys.j) {
|
||||
dispatch(changeDirection(directions.DOWN));
|
||||
}
|
||||
if (key === keys.LEFT || key === keys.h) {
|
||||
dispatch(changeDirection(directions.LEFT));
|
||||
}
|
||||
if (key === keys.RIGHT || key === keys.l) {
|
||||
dispatch(changeDirection(directions.RIGHT));
|
||||
}
|
||||
if (key === keys.s) {
|
||||
dispatch(stopSnake());
|
||||
}
|
||||
if (key === keys.p) {
|
||||
dispatch(pauseSnake());
|
||||
}
|
||||
if (key === keys.r) {
|
||||
dispatch(startSnake());
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user