diff --git a/src/components/ArrowCluster.js b/src/components/ArrowCluster.js
new file mode 100644
index 0000000..4f9412d
--- /dev/null
+++ b/src/components/ArrowCluster.js
@@ -0,0 +1,15 @@
+// const ArrowButton = styled(Button)`
+// min-width: 5rem;
+// `;
+// const ArrowCluster = () => (
+//
+//
+//
+//
Left
+//
Down
+//
Right
+//
+//
+// );
diff --git a/src/components/ControlPanel.js b/src/components/ControlPanel.js
new file mode 100644
index 0000000..180201f
--- /dev/null
+++ b/src/components/ControlPanel.js
@@ -0,0 +1,22 @@
+import React from "react";
+import styled from "styled-components";
+import Button, { ToggleButton } from "../components/Button.js";
+
+const Layout = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+export const ControlPanel = ({ updateFrameSnake, paused, pauseSnake, stopSnake, startSnake, fps }) => (
+
+ Control Panel
+
+ {paused ? "Resume" : "Pause"}
+
+
+
+ FPS: {fps}
+
+);
+
+export default ControlPanel;
diff --git a/src/components/HeaderMenu.js b/src/components/HeaderMenu.js
index 86b4d69..b37f070 100644
--- a/src/components/HeaderMenu.js
+++ b/src/components/HeaderMenu.js
@@ -34,11 +34,6 @@ const HeaderMenu = () => (
Home
- {
- //
- }
diff --git a/src/components/Scoreboard.js b/src/components/Scoreboard.js
index d8e4f95..96bcf0c 100644
--- a/src/components/Scoreboard.js
+++ b/src/components/Scoreboard.js
@@ -47,18 +47,12 @@ const Card = styled.div`
border-bottom: dotted 2px #ad5a75;
z-index:1;
}
-
- // &::after {
- // content: "${props => props.children}";
- // display: block;
- // position: absolute;
- // font-size: 3em;
- // }
`;
const StyledScoreboard = styled.div`
height: ${props => (props.zoom || 1) * DEFAULT_HEIGHT}rem;
margin-bottom: 1rem;
+ white-space: nowrap;
& > div {
position: relative;
diff --git a/src/components/Stage.js b/src/components/Stage.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/containers/App.js b/src/containers/App.js
index ac88cc8..3009b49 100644
--- a/src/containers/App.js
+++ b/src/containers/App.js
@@ -8,8 +8,6 @@ import Header from "./Header";
import Footer from "./Footer";
import Page from "../components/Page";
import Home from "./Home";
-import Keyboards from "./Keyboards";
-import KeyboardDetails from "./KeyboardDetails";
import Snake from "./Snake";
const App = ({ title, onNewTitle, getKeyboards, keyboards }) => (
@@ -18,10 +16,7 @@ const App = ({ title, onNewTitle, getKeyboards, keyboards }) => (
-
-
-
@@ -37,8 +32,4 @@ const ConnectedApp = connect(
mapActions
)(App);
-// const App = () => Hello Redux!!!
;
-
-console.log("env", process.env.NODE_ENV);
-process.env.NODE_ENV === "development" ? console.log("development") : console.log("production");
export default (process.env.NODE_ENV === "development" ? hot(ConnectedApp) : ConnectedApp);
diff --git a/src/containers/Document.js b/src/containers/Document.js
index cdf578d..1499c60 100644
--- a/src/containers/Document.js
+++ b/src/containers/Document.js
@@ -1,62 +1,20 @@
import React from "react";
import { Provider } from "react-redux";
import { hot } from "react-hot-loader/root";
+import { ConnectedRouter } from "connected-react-router";
-import { createStore, combineReducers, compose, applyMiddleware } from "redux";
-import { createBrowserHistory } from "history";
-
-import { connectRouter, routerMiddleware, ConnectedRouter } from "connected-react-router";
-import { reducers as keyboardReducers, middleware as keyboardMiddleware } from "../redux/keyboards.js";
-import { middleware as apiMiddleware } from "../redux/api.js";
-import { reducers as uiReducers } from "../redux/ui.js";
-import { module as snakeModule } from "../redux/snake.js";
-// import { reducers as gameReducers, middleware as gameMiddleware } from "../redux/game.js";
-import { module as gameModule } from "../redux/game.js";
-import { module as homeModule } from "../redux/home.js";
+import { createStore } from "../redux/store";
import { ThemeProvider } from "styled-components";
import { themes } from "../theming/theme.js";
import GlobalStyle from "../theming/GlobalStyle.js";
-window.module = gameModule;
-
-const history = createBrowserHistory({
- basename: process.env.NODE_ENV === "production" ? "/redux/" : "/"
-});
-
-const store = createStore(
- combineReducers({
- keyboards: keyboardReducers,
- ui: uiReducers,
- router: connectRouter(history),
- ...snakeModule.reducers,
- ...gameModule.reducers,
- ...homeModule.reducers
- }),
- {},
- compose.apply(
- this,
- [
- applyMiddleware(
- routerMiddleware(history),
- apiMiddleware,
- keyboardMiddleware,
- snakeModule.middlewares,
- gameModule.middlewares,
- homeModule.middlewares
- ),
- process.env.NODE_ENV !== "production" &&
- window.__REDUX_DEVTOOLS_EXTENSION__ &&
- window.__REDUX_DEVTOOLS_EXTENSION__()
- ].filter(m => m)
- )
-);
-window.st = store;
+const store = createStore();
const Document = ({ children }) => (
-
+
{children}
@@ -64,33 +22,4 @@ const Document = ({ children }) => (
);
-console.log("env", process.env.NODE_ENV);
-process.env.NODE_ENV === "development" ? console.log("development") : console.log("production");
export default (process.env.NODE_ENV === "development" ? hot(Document) : Document);
-// export default Document;
-
-if (module.hot) {
- module.hot.accept(
- [
- "../redux/snake.js",
- "./Snake.js",
- "./App.js",
- "./Document.js",
- "../redux/game.js",
- "../redux/module.js",
- "../redux/home.js"
- ],
- () => {
- store.replaceReducer(
- combineReducers({
- keyboards: keyboardReducers,
- ui: uiReducers,
- router: connectRouter(history),
- ...snakeModule.reducers,
- ...gameModule.reducers,
- ...homeModule.reducers
- })
- );
- }
- );
-}
diff --git a/src/containers/Header.js b/src/containers/Header.js
index 61d7f4c..06296ed 100644
--- a/src/containers/Header.js
+++ b/src/containers/Header.js
@@ -5,19 +5,27 @@ import styled from "styled-components";
const HeaderContainer = styled.header`
background-color: ${props => props.theme.header.background};
- display: flex;
min-height: 6.25rem;
- align-content: space-between;
- place-content: space-between;
- align-items: center;
padding: 1rem;
box-sizing: border-box;
`;
+const HeaderContent = styled.div`
+ margin: 0 auto;
+ width: 100%;
+ max-width: 60rem;
+ display: flex;
+ align-content: space-between;
+ place-content: space-between;
+ align-items: center;
+`;
+
const Header = () => (
-
-
+
+
+
+
);
diff --git a/src/containers/Home.js b/src/containers/Home.js
index 3878e8f..ff23e66 100644
--- a/src/containers/Home.js
+++ b/src/containers/Home.js
@@ -1,53 +1,10 @@
import React from "react";
import Title from "../components/Title.js";
-import Button from "../components/Button.js";
-import Link from "../components/Link.js";
-import { connect } from "react-redux";
-import { module as game, startLoop, stopLoop, registerCaller, unregisterCaller, setFps } from "../redux/game.js";
-import { module as home, INCREMENT_COUNTER, resetCounter } from "../redux/home.js";
-
-const Home = ({
- startLoop,
- stopLoop,
- loopId,
- registerCaller,
- counter,
- unregisterCaller,
- setFps,
- fps,
- callers,
- resetCounter
-}) => (
+export const Home = () => (
Home Page
-
- fps: {fps}
-
-
- loopId: {loopId}
-
-
- counter: {counter} Reset
-
-
- callers: {callers}
-
-
-
-
-
-
-
);
-export default connect(
- state => ({
- loopId: state[game.name].loopId,
- fps: state[game.name].fps,
- callers: state[game.name].callers,
- counter: state[home.name] && state[home.name].counter
- }),
- { startLoop, stopLoop, registerCaller, unregisterCaller, setFps, resetCounter }
-)(Home);
+export default Home;
diff --git a/src/containers/KeyboardDetails.js b/src/containers/KeyboardDetails.js
deleted file mode 100644
index 0fe9b36..0000000
--- a/src/containers/KeyboardDetails.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import React from "react";
-import { connect } from "react-redux";
-import { getKeyboards } from "../redux/keyboards.js";
-import Spinner from "../components/Spinner.js";
-import Title from "../components/Title.js";
-
-const KeyboardDetailsContainer = ({ children }) => (
-
- Keyboard Details
- {children}
-
-);
-
-class KeyboardDetails extends React.Component {
- constructor(props) {
- super(props);
- this.id = parseInt(this.props.match.params.id, 10);
- }
-
- componentDidMount() {
- this.props.getKeyboards();
- }
-
- render() {
- const keyboardsLoaded = !!this.props.keyboards;
- const loading = !keyboardsLoaded || this.props.spinner;
-
- const keyboard = this.props.keyboards && this.props.keyboards.find(k => k.id === this.id);
- const keyboardExists = keyboardsLoaded && !!keyboard;
-
- if (loading) {
- return (
-
-
-
- );
- }
-
- if (!keyboardExists) {
- return (
-
- Keyboard not found...
-
- );
- }
-
- return (
-
- {keyboard.maker}
-
- );
- }
-}
-
-const state = ({ keyboards, ui }) => ({
- keyboards: keyboards.list,
- spinner: ui.spinner
-});
-
-const actions = {
- getKeyboards
-};
-
-export default connect(
- state,
- actions
-)(KeyboardDetails);
diff --git a/src/containers/Keyboards.js b/src/containers/Keyboards.js
deleted file mode 100644
index acc5798..0000000
--- a/src/containers/Keyboards.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from "react";
-import { connect } from "react-redux";
-import Title from "../components/Title.js";
-import List from "../components/List.js";
-import Button from "../components/Button.js";
-import Link from "../components/Link.js";
-import Spinner from "../components/Spinner.js";
-import { getKeyboards, showKeyboard } from "../redux/keyboards.js";
-
-const KeyboardListItem = ({ item, showKeyboard }) => (
-
-
- {item.maker} - {item.model}
-
-
-);
-
-const NoKeyboardsFound = ({ spinner }) => {
- if (spinner) {
- return Loading...
;
- }
- return There are no keyboards
;
-};
-
-class Keyboards extends React.Component {
- componentDidMount() {
- this.props.getKeyboards();
- }
- render() {
- const { keyboards, spinner, getKeyboards } = this.props;
- return (
-
- Keyboard List
-
-
- {spinner && }
-
- );
- }
-}
-const mapState = ({ keyboards, ui }) => ({
- keyboards: keyboards.list,
- spinner: ui.spinner
-});
-
-const mapActions = {
- getKeyboards,
- showKeyboard
-};
-
-export default connect(
- mapState,
- mapActions
-)(Keyboards);
diff --git a/src/containers/Snake.js b/src/containers/Snake.js
index 0a05df6..246d1da 100644
--- a/src/containers/Snake.js
+++ b/src/containers/Snake.js
@@ -15,11 +15,23 @@ import {
} from "../redux/snake.js";
/* Components */
-import Title from "../components/Title.js";
-import Button, { ToggleButton } from "../components/Button.js";
+import ControlPanel from "../components/ControlPanel.js";
import Scoreboard from "../components/Scoreboard.js";
-const Row = styled.div``;
+const Layout = styled.div`
+ display: flex;
+ width: 100%;
+ place-content: space-between;
+`;
+
+const SidePanel = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const Row = styled.div`
+ white-space: nowrap;
+`;
const Cell = styled.div`
width: ${({ theme }) => theme.snake.cell.size};
@@ -35,7 +47,7 @@ const Cell = styled.div`
const SnakePart = styled.div`
width: ${({ theme }) => theme.snake.cell.size};
height: ${({ theme }) => theme.snake.cell.size};
- background-color: ${props => props.value && `hsl(340, ${props.value.slice(1)}%, 65%)`};
+ background-color: ${props => props.value && `hsl(340, ${props.value.brightness}%, 65%)`};
box-shadow: 1px 1px 2px #ddd inset, -1px -1px 2px gray inset;
`;
@@ -48,7 +60,7 @@ const Apple = styled.div.attrs({ role: "img", "aria-label": "apple" })`
line-height: 1.8rem;
padding-left: 0.0625rem;
&:after {
- content: "${props => (!props.died ? "🍎" : "")}";
+ content: "${props => (!props.died ? "🍎" : "🐛")}";
display: inline-block;
}
`;
@@ -60,44 +72,30 @@ const Skull = styled.div.attrs({ role: "img", "aria-label": "skull" })`
font-size: 1.3rem;
vertical-align: top;
line-height: 1.8rem;
- background-color: ${props => props.value && `hsl(0, 0%, ${100 - props.value.slice(1)}% )`};
+ background-color: ${props => props.value && `hsl(0, 0%, ${100 - props.value.brightness}% )`};
padding-left: 0.0625rem;
box-shadow: 1px 1px 2px #ddd inset, -1px -1px 2px gray inset;
&:after {
- content: "💀";
+ content: "${props => props.value && props.value.index === 0 && "💀"}";
display: inline-block;
}
`;
-const Grid = ({ data, died }) =>
- data.map((r, y) => (
-
- {r.map((c, x) => (
- |
- {c === "a" && }
- {!died && c[0] === "s" && }
- {died && c[0] === "s" && }
- |
- ))}
-
- ));
-
-// const ArrowButton = styled(Button)`
-// min-width: 5rem;
-// `;
-
-// const ArrowCluster = () => (
-//
-//
-//
-//
Left
-//
Down
-//
Right
-//
-//
-// );
+const Grid = ({ data, died }) => (
+
+ {data.map((r, y) => (
+
+ {r.map((c, x) => (
+ |
+ {c.type === "apple" && }
+ {!died && c.type === "snake" && }
+ {died && c.type === "snake" && }
+ |
+ ))}
+
+ ))}
+
+);
class Snake extends React.Component {
constructor(props) {
@@ -133,15 +131,20 @@ class Snake extends React.Component {
return (
-
-
-
-
- {paused ? "Resume" : "Pause"}
-
-
-
- FPS: {fps}
+
+
+
+
+
+
+
);
}
diff --git a/src/redux/api.js b/src/redux/api.js
deleted file mode 100644
index e24753a..0000000
--- a/src/redux/api.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const FETCH_DATA = "[API] FETCH_DATA";
-
-export const fetchData = ({ url, onError, onSuccess }) => ({ type: FETCH_DATA, url, onError, onSuccess });
-
-const keyboardList = [
- { id: 1, maker: "Percent Studio", model: "Canoe" },
- { id: 2, maker: "Gray Studio", model: "Space65" }
-];
-
-export const middleware = ({ dispatch }) => next => action => {
- next(action);
-
- if (action.type === FETCH_DATA) {
- if (action.url === "/keyboards") {
- setTimeout(() => {
- dispatch({ type: action.onSuccess, data: keyboardList });
- }, 3000);
- } else {
- dispatch({ type: action.onError, data: new Error(`Unable to fetch data from url ${action.url}`) });
- }
- }
-};
diff --git a/src/redux/game.backup.js b/src/redux/game.backup.js
deleted file mode 100644
index ca64a3e..0000000
--- a/src/redux/game.backup.js
+++ /dev/null
@@ -1,44 +0,0 @@
-const MODULE_NAME = "GAME";
-
-const initialState = {
- started: false,
- handlers: []
-};
-
-const GAME_UPDATE_LOOP_STATE = "GAME_UPDATE_LOOP_STATE";
-const GAME_START_LOOP = "GAME_START_LOOP";
-const GAME_STOP_LOOP = "GAME_STOP_LOOP";
-const GAME_REGISTER_LOOP_HANDLER = "GAME_REGISTER_LOOP_HANDLER";
-const GAME_UNREGISTER_LOOP_HANDLER = "GAME_UNREGISTER_LOOP_HANDLER";
-
-export const gameUpdateLoopState = started => ({ type: GAME_UPDATE_LOOP_STATE, started });
-export const gameStartLoop = () => ({ type: GAME_START_LOOP });
-export const gameStopLoop = () => ({ type: GAME_STOP_LOOP });
-export const gameRegisterLoopHandler = handler => ({ type: GAME_REGISTER_LOOP_HANDLER, handler });
-export const gameUnregisterLoopHandler = handler => ({ type: GAME_UNREGISTER_LOOP_HANDLER, handler });
-
-export const reducers = (state = initialState, action) => {
- if (action.type === GAME_UPDATE_LOOP_STATE) {
- return { ...state, started: action.started };
- }
- if (action.type === GAME_REGISTER_LOOP_HANDLER) {
- return { ...state, handlers: [...state.handlers, action.handler] };
- }
- if (action.type === GAME_UNREGISTER_LOOP_HANDLER) {
- return { ...state, handlers: state.handlers.filter(handler => handler !== action.handler) };
- }
- return state;
-};
-
-export const middleware = ({ dispatch, getState }) => next => action => {
- next(action);
-
- if (action.type === GAME_START_LOOP) {
- if (getState()[MODULE_NAME].started) dispatch(gameUpdateLoopState(true));
- }
- if (action.type === GAME_STOP_LOOP) {
- dispatch(gameUpdateLoopState(false));
- }
-};
-
-reducers.module = middleware.module = MODULE_NAME;
diff --git a/src/redux/game.js b/src/redux/game.js
index 89cab62..a732695 100644
--- a/src/redux/game.js
+++ b/src/redux/game.js
@@ -6,7 +6,7 @@ export const module = new Module(MODULE_NAME, {
loopId: null,
callers: [],
keyPressSubscribers: [],
- fps: 5
+ fps: 8
});
export const keys = {
diff --git a/src/redux/home.js b/src/redux/home.js
deleted file mode 100644
index 2b83338..0000000
--- a/src/redux/home.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import Module from "./module.js";
-
-const MODULE_NAME = "HOME";
-
-export const module = new Module(MODULE_NAME, {
- counter: 0
-});
-
-export const [INCREMENT_COUNTER, incrementCounter] = module.action("INCREMENT_COUNTER");
-export const [RESET_COUNTER, resetCounter] = module.action("RESET_COUNTER");
-
-module.reducer(INCREMENT_COUNTER, state => {
- return { ...state, counter: state.counter + 1 };
-});
-
-module.reducer(RESET_COUNTER, state => {
- return { ...state, counter: 0 };
-});
-
-export default module;
diff --git a/src/redux/keyboards.js b/src/redux/keyboards.js
deleted file mode 100644
index a3349cf..0000000
--- a/src/redux/keyboards.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import { push } from "connected-react-router";
-import { showSpinner, hideSpinner } from "./ui.js";
-import { fetchData } from "./api.js";
-
-const GET_KEYBOARDS = "[Keyboards] GET_KEYBOARDS";
-const LOAD_KEYBOARDS = "[Keyboards] LOAD_KEYBOARDS";
-const LOAD_KEYBOARDS_SUCCEEDED = "[Keyboards] LOAD_KEYBOARDS_SUCCEEDED";
-const LOAD_KEYBOARDS_FAILED = "[Keyboards] LOAD_KEYBOARDS_FAILED";
-const UPDATE_KEYBOARDS = "[Keyboards] UPDATE_KEYBOARDS";
-const GET_KEYBOARD = "[Keyboards] GET_KEYBOARD";
-const SHOW_KEYBOARD_DETAILS = "[Keyboards] SHOW_KEYBOARD_DETAILS";
-
-export const loadKeyboards = ({ onError, onSuccess }) => ({ type: LOAD_KEYBOARDS, onError, onSuccess });
-export const getKeyboards = ({ force } = { force: false }) => ({ type: GET_KEYBOARDS, force });
-export const updateKeyboards = keyboards => ({ type: UPDATE_KEYBOARDS, keyboards });
-export const showKeyboard = id => ({ type: SHOW_KEYBOARD_DETAILS, id });
-export const getKeyboard = id => ({ type: GET_KEYBOARD, id });
-
-export const middleware = ({ dispatch, getState }) => next => action => {
- next(action);
-
- if (action.type === SHOW_KEYBOARD_DETAILS) {
- dispatch(push(`/keyboards/${action.id}`));
- }
- if (action.type === GET_KEYBOARDS) {
- const { keyboards } = getState();
- if (!keyboards.list || action.force) {
- dispatch(showSpinner());
- dispatch(loadKeyboards({ onError: LOAD_KEYBOARDS_FAILED, onSuccess: LOAD_KEYBOARDS_SUCCEEDED }));
- }
- }
-
- if (action.type === LOAD_KEYBOARDS) {
- dispatch(fetchData({ url: "/keyboards", onError: action.onError, onSuccess: action.onSuccess }));
- }
-
- if (action.type === LOAD_KEYBOARDS_SUCCEEDED) {
- dispatch(hideSpinner());
- dispatch(updateKeyboards(action.data));
- }
-};
-
-export const reducers = (keyboards = {}, action) => {
- if (action.type === UPDATE_KEYBOARDS) {
- return {
- ...keyboards,
- list: action.keyboards,
- error: undefined
- };
- }
- if (action.type === LOAD_KEYBOARDS_FAILED) {
- return {
- ...keyboards,
- list: undefined,
- error: action.err
- };
- }
- if (action.type === GET_KEYBOARD) {
- return {
- ...keyboards
- };
- }
-
- return keyboards;
-};
diff --git a/src/redux/snake.backup.js b/src/redux/snake.backup.js
deleted file mode 100644
index 083f280..0000000
--- a/src/redux/snake.backup.js
+++ /dev/null
@@ -1,215 +0,0 @@
-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";
-const START_ANIMATION_SNAKE = "[Snake] START_ANIMATION_SNAKE";
-const STOP_ANIMATION_SNAKE = "[Snake] STOP_ANIMATION_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 startAnimationSnake = () => ({ type: START_ANIMATION_SNAKE });
-export const stopAnimationSnake = () => ({ type: STOP_ANIMATION_SNAKE });
-
-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];
-};
-
-// eslint-disable-next-line max-statements
-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 === START_ANIMATION_SNAKE) {
- if (state.isAnimating) {
- return state;
- }
- return { ...state, isAnimating: true };
- }
-
- if (action.type === STOP_ANIMATION_SNAKE) {
- if (!state.isAnimating) {
- return state;
- }
- return { ...state, isAnimating: false };
- }
-
- 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 };
- }
-
- if (action.type === "UPDATE_STATE") {
- console.log("action.state", action.state);
- return action.state;
- }
-
- return state;
-};
-
-export const middleware = ({ dispatch, getState }) => next => action => {
- next(action);
-
- if (action.type === START_SNAKE) {
- const { started } = getState().snakeGame;
- if (started) {
- return;
- }
-
- dispatch(resetSnake());
- animate(1, () => dispatch(nextFrameSnake()));
- }
-
- if (action.type === NEXT_FRAME_SNAKE) {
- const {
- snakeGame: { started, paused, gameId, fps, isAnimating }
- } = getState();
- if (isAnimating) {
- return;
- }
- dispatch(startAnimationSnake());
- !paused && dispatch(updateFrameSnake());
- started &&
- animate(fps, () => {
- dispatch(stopAnimationSnake());
- 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();
-};
diff --git a/src/redux/snake.js b/src/redux/snake.js
index 5e6d050..5e4bb06 100644
--- a/src/redux/snake.js
+++ b/src/redux/snake.js
@@ -17,6 +17,8 @@ const initialState = {
paused: false,
vX: 1,
vY: 0,
+ vXNext: undefined,
+ vYNext: undefined,
snake: [],
score: 0,
apple: []
@@ -42,7 +44,7 @@ export const keys = {
k: "k",
l: "l",
- p: "p",
+ snakePart: "snakePart",
s: "s",
r: "r"
};
@@ -52,6 +54,8 @@ function randomPosition(size) {
}
const comparePositions = (pos1, pos2) => pos1[0] === pos2[0] && pos1[1] === pos2[1];
+const brightness = (l, i) => 20 + Math.round(0.8 * ((i + 1) / l) * 100);
+
function createGrid(size) {
const rows = [];
for (let y = 0; y < size; y += 1) {
@@ -118,55 +122,87 @@ module.reducer(RESET_SNAKE, (state, options) => {
const apple = options.started ? randomPosition(state.size) : initialState.apple;
const snake = options.started ? [[0, 0]] : initialState.snake;
- markCell(grid, apple, "a");
- snake.forEach(p => markCell(grid, p, "s"));
+ markCell(grid, apple, { type: "apple" });
+ snake.forEach((snakePart, i) =>
+ markCell(grid, snakePart, { type: "snake", index: snake.length - 1 - i, brightness: brightness(snake.length, i) })
+ );
return { ...state, ...initialState, grid, snake, apple, ...options };
});
module.reducer(CHANGE_DIRECTION, (state, { direction }) => {
- const { vX, vY } = state;
+ const { vX, vY, vXNext, vYNext } = state;
+ if (vXNext !== undefined || vYNext !== undefined) {
+ return state;
+ }
if (direction === directions.UP && vY !== 1) {
- return { ...state, vX: 0, vY: -1 };
+ return { ...state, vXNext: 0, vYNext: -1 };
}
if (direction === directions.DOWN && vY !== -1) {
- return { ...state, vX: 0, vY: 1 };
+ return { ...state, vXNext: 0, vYNext: 1 };
}
if (direction === directions.LEFT && vX !== 1) {
- return { ...state, vX: -1, vY: 0 };
+ return { ...state, vXNext: -1, vYNext: 0 };
}
if (direction === directions.RIGHT && vX !== -1) {
- return { ...state, vX: 1, vY: 0 };
+ return { ...state, vXNext: 1, vYNext: 0 };
}
return state;
});
module.reducer(UPDATE_FRAME_SNAKE, state => {
- const { snake, vX, vY, size } = state;
+ const { snake, vX, vY, vXNext, vYNext, size } = state;
let { apple, started, died, score } = 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 {
+ const nextPosition = moveSnakePart(
+ snake[snake.length - 1],
+ vXNext !== undefined ? vXNext : vX,
+ vYNext !== undefined ? vYNext : vY,
+ size
+ );
+ const newSnake = [...snake, nextPosition];
+
+ /* If the snake did not eat an apple then remove the last part of its tail */
+ if (!comparePositions(nextPosition, apple)) {
newSnake.shift();
}
- if (newSnake.filter(p => comparePositions(p, nextPosition)).length) {
+
+ /* Check if the snake hits itself */
+ if (newSnake.slice(0, -1).filter((snakePart, index) => comparePositions(snakePart, 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)));
+
+ if (comparePositions(nextPosition, apple)) {
+ apple = randomPosition(size);
+ // eslint-disable-next-line no-loop-func
+ while (newSnake.filter(snakePart => comparePositions(snakePart, apple)).length) {
+ apple = randomPosition(size);
+ }
+ }
+ markCell(grid, apple, { type: "apple" });
+ newSnake.forEach((snakePart, i) =>
+ markCell(grid, snakePart, {
+ type: "snake",
+ index: newSnake.length - 1 - i,
+ brightness: brightness(newSnake.length, i)
+ })
+ );
score = newSnake.length;
- return { ...state, grid, vX, vY, snake: newSnake, apple, started, died, score };
+ return {
+ ...state,
+ grid,
+ vX: vXNext !== undefined ? vXNext : vX,
+ vY: vYNext !== undefined ? vYNext : vY,
+ vXNext: undefined,
+ vYNext: undefined,
+ snake: newSnake,
+ apple,
+ started,
+ died,
+ score
+ };
});
/* === Middleware =============================================================================== */
@@ -217,7 +253,7 @@ module.middleware(KEY_PRESSED_SNAKE, (dispatch, _, { key }) => {
if (key === keys.s) {
dispatch(stopSnake());
}
- if (key === keys.p) {
+ if (key === keys.snakePart) {
dispatch(pauseSnake());
}
if (key === keys.r) {
diff --git a/src/redux/store.js b/src/redux/store.js
new file mode 100644
index 0000000..9b32efc
--- /dev/null
+++ b/src/redux/store.js
@@ -0,0 +1,51 @@
+import { createStore as createReduxStore, combineReducers, compose, applyMiddleware } from "redux";
+import { createBrowserHistory } from "history";
+
+import { connectRouter, routerMiddleware } from "connected-react-router";
+import { reducers as uiReducers } from "./ui.js";
+import { module as snakeModule } from "./snake.js";
+import { module as gameModule } from "./game.js";
+
+export const createStore = () => {
+ const history = createBrowserHistory({
+ basename: process.env.NODE_ENV === "production" ? "/redux/" : "/"
+ });
+
+ const store = createReduxStore(
+ combineReducers({
+ router: connectRouter(history),
+ ui: uiReducers,
+ ...snakeModule.reducers,
+ ...gameModule.reducers
+ }),
+ {},
+ compose.apply(
+ this,
+ [
+ applyMiddleware(routerMiddleware(history), snakeModule.middlewares, gameModule.middlewares),
+ process.env.NODE_ENV !== "production" &&
+ window.__REDUX_DEVTOOLS_EXTENSION__ &&
+ window.__REDUX_DEVTOOLS_EXTENSION__()
+ ].filter(m => m)
+ )
+ );
+
+ if (module.hot) {
+ module.hot.accept(["./ui.js", "./snake.js", "./game.js", "./module.js"], () => {
+ store.replaceReducer(
+ combineReducers({
+ ui: uiReducers,
+ router: connectRouter(history),
+ ...snakeModule.reducers,
+ ...gameModule.reducers
+ })
+ );
+ });
+ }
+
+ store.history = history;
+
+ return store;
+};
+
+export default createStore;