Cleaning up, refactoring and adding a sidepanel
This commit is contained in:
15
src/components/ArrowCluster.js
Normal file
15
src/components/ArrowCluster.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// const ArrowButton = styled(Button)`
|
||||||
|
// min-width: 5rem;
|
||||||
|
// `;
|
||||||
|
// const ArrowCluster = () => (
|
||||||
|
// <div>
|
||||||
|
// <div>
|
||||||
|
// <ArrowButton>Up</ArrowButton>
|
||||||
|
// </div>
|
||||||
|
// <div>
|
||||||
|
// <ArrowButton>Left</ArrowButton>
|
||||||
|
// <ArrowButton>Down</ArrowButton>
|
||||||
|
// <ArrowButton>Right</ArrowButton>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
22
src/components/ControlPanel.js
Normal file
22
src/components/ControlPanel.js
Normal file
@@ -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 }) => (
|
||||||
|
<Layout>
|
||||||
|
<h1>Control Panel</h1>
|
||||||
|
<ToggleButton toggle={paused} onClick={pauseSnake}>
|
||||||
|
{paused ? "Resume" : "Pause"}
|
||||||
|
</ToggleButton>
|
||||||
|
<Button onClick={stopSnake}>Stop</Button>
|
||||||
|
<Button onClick={startSnake}>Reset</Button>
|
||||||
|
<span>FPS: {fps}</span>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ControlPanel;
|
||||||
@@ -34,11 +34,6 @@ const HeaderMenu = () => (
|
|||||||
Home
|
Home
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{
|
|
||||||
// <MenuItem>
|
|
||||||
// <MenuButton to="/keyboards">Keyboards</MenuButton>
|
|
||||||
// </MenuItem>
|
|
||||||
}
|
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<MenuButton to="/snake">Snake</MenuButton>
|
<MenuButton to="/snake">Snake</MenuButton>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@@ -47,18 +47,12 @@ const Card = styled.div`
|
|||||||
border-bottom: dotted 2px #ad5a75;
|
border-bottom: dotted 2px #ad5a75;
|
||||||
z-index:1;
|
z-index:1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// &::after {
|
|
||||||
// content: "${props => props.children}";
|
|
||||||
// display: block;
|
|
||||||
// position: absolute;
|
|
||||||
// font-size: 3em;
|
|
||||||
// }
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledScoreboard = styled.div`
|
const StyledScoreboard = styled.div`
|
||||||
height: ${props => (props.zoom || 1) * DEFAULT_HEIGHT}rem;
|
height: ${props => (props.zoom || 1) * DEFAULT_HEIGHT}rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
0
src/components/Stage.js
Normal file
0
src/components/Stage.js
Normal file
@@ -8,8 +8,6 @@ import Header from "./Header";
|
|||||||
import Footer from "./Footer";
|
import Footer from "./Footer";
|
||||||
import Page from "../components/Page";
|
import Page from "../components/Page";
|
||||||
import Home from "./Home";
|
import Home from "./Home";
|
||||||
import Keyboards from "./Keyboards";
|
|
||||||
import KeyboardDetails from "./KeyboardDetails";
|
|
||||||
import Snake from "./Snake";
|
import Snake from "./Snake";
|
||||||
|
|
||||||
const App = ({ title, onNewTitle, getKeyboards, keyboards }) => (
|
const App = ({ title, onNewTitle, getKeyboards, keyboards }) => (
|
||||||
@@ -18,10 +16,7 @@ const App = ({ title, onNewTitle, getKeyboards, keyboards }) => (
|
|||||||
<Page>
|
<Page>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={Home} />
|
<Route exact path="/" component={Home} />
|
||||||
<Route path="/keyboards/:id" exact component={KeyboardDetails} />
|
|
||||||
<Route path="/keyboards" exact component={Keyboards} />
|
|
||||||
<Route path="/snake" exact component={Snake} />
|
<Route path="/snake" exact component={Snake} />
|
||||||
<Route path="/test/snake" exact component={Snake} />
|
|
||||||
</Switch>
|
</Switch>
|
||||||
</Page>
|
</Page>
|
||||||
<Footer />
|
<Footer />
|
||||||
@@ -37,8 +32,4 @@ const ConnectedApp = connect(
|
|||||||
mapActions
|
mapActions
|
||||||
)(App);
|
)(App);
|
||||||
|
|
||||||
// const App = () => <h1>Hello Redux!!!</h1>;
|
|
||||||
|
|
||||||
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);
|
export default (process.env.NODE_ENV === "development" ? hot(ConnectedApp) : ConnectedApp);
|
||||||
|
|||||||
@@ -1,62 +1,20 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { hot } from "react-hot-loader/root";
|
import { hot } from "react-hot-loader/root";
|
||||||
|
import { ConnectedRouter } from "connected-react-router";
|
||||||
|
|
||||||
import { createStore, combineReducers, compose, applyMiddleware } from "redux";
|
import { createStore } from "../redux/store";
|
||||||
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 { ThemeProvider } from "styled-components";
|
import { ThemeProvider } from "styled-components";
|
||||||
import { themes } from "../theming/theme.js";
|
import { themes } from "../theming/theme.js";
|
||||||
import GlobalStyle from "../theming/GlobalStyle.js";
|
import GlobalStyle from "../theming/GlobalStyle.js";
|
||||||
|
|
||||||
window.module = gameModule;
|
const store = createStore();
|
||||||
|
|
||||||
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 Document = ({ children }) => (
|
const Document = ({ children }) => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ThemeProvider theme={themes.main}>
|
<ThemeProvider theme={themes.main}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={store.history}>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
{children}
|
{children}
|
||||||
</ConnectedRouter>
|
</ConnectedRouter>
|
||||||
@@ -64,33 +22,4 @@ const Document = ({ children }) => (
|
|||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
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 (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
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,19 +5,27 @@ import styled from "styled-components";
|
|||||||
|
|
||||||
const HeaderContainer = styled.header`
|
const HeaderContainer = styled.header`
|
||||||
background-color: ${props => props.theme.header.background};
|
background-color: ${props => props.theme.header.background};
|
||||||
display: flex;
|
|
||||||
min-height: 6.25rem;
|
min-height: 6.25rem;
|
||||||
align-content: space-between;
|
|
||||||
place-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
box-sizing: border-box;
|
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 = () => (
|
const Header = () => (
|
||||||
<HeaderContainer>
|
<HeaderContainer>
|
||||||
<Logo />
|
<HeaderContent>
|
||||||
<HeaderMenu />
|
<Logo />
|
||||||
|
<HeaderMenu />
|
||||||
|
</HeaderContent>
|
||||||
</HeaderContainer>
|
</HeaderContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Title from "../components/Title.js";
|
import Title from "../components/Title.js";
|
||||||
import Button from "../components/Button.js";
|
|
||||||
import Link from "../components/Link.js";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
export const Home = () => (
|
||||||
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
|
|
||||||
}) => (
|
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Title large>Home Page</Title>
|
<Title large>Home Page</Title>
|
||||||
<p>
|
|
||||||
<b>fps:</b> {fps}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b>loopId:</b> {loopId}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b>counter:</b> {counter} <Link onClick={resetCounter}>Reset</Link>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b>callers:</b> {callers}
|
|
||||||
</p>
|
|
||||||
<Button onClick={startLoop}>Start</Button>
|
|
||||||
<Button onClick={stopLoop}>Stop</Button>
|
|
||||||
<Button onClick={() => registerCaller(INCREMENT_COUNTER)}>Register</Button>
|
|
||||||
<Button onClick={() => unregisterCaller(INCREMENT_COUNTER)}>Unregister</Button>
|
|
||||||
<Button onClick={() => setFps(fps - 1)}>Slower</Button>
|
|
||||||
<Button onClick={() => setFps(fps + 1)}>Faster</Button>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default connect(
|
export default Home;
|
||||||
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);
|
|
||||||
|
|||||||
@@ -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 }) => (
|
|
||||||
<React.Fragment>
|
|
||||||
<Title large>Keyboard Details</Title>
|
|
||||||
{children}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<KeyboardDetailsContainer>
|
|
||||||
<Spinner />
|
|
||||||
</KeyboardDetailsContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!keyboardExists) {
|
|
||||||
return (
|
|
||||||
<KeyboardDetailsContainer>
|
|
||||||
<p>Keyboard not found...</p>
|
|
||||||
</KeyboardDetailsContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<KeyboardDetailsContainer>
|
|
||||||
<p>{keyboard.maker}</p>
|
|
||||||
</KeyboardDetailsContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = ({ keyboards, ui }) => ({
|
|
||||||
keyboards: keyboards.list,
|
|
||||||
spinner: ui.spinner
|
|
||||||
});
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
getKeyboards
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state,
|
|
||||||
actions
|
|
||||||
)(KeyboardDetails);
|
|
||||||
@@ -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 }) => (
|
|
||||||
<List.Item key={item.id}>
|
|
||||||
<Link to={`/keyboards/${item.id}`}>
|
|
||||||
{item.maker} - {item.model}
|
|
||||||
</Link>
|
|
||||||
</List.Item>
|
|
||||||
);
|
|
||||||
|
|
||||||
const NoKeyboardsFound = ({ spinner }) => {
|
|
||||||
if (spinner) {
|
|
||||||
return <p>Loading...</p>;
|
|
||||||
}
|
|
||||||
return <p>There are no keyboards</p>;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Keyboards extends React.Component {
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.getKeyboards();
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const { keyboards, spinner, getKeyboards } = this.props;
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<Title large>Keyboard List</Title>
|
|
||||||
<List items={keyboards} Item={KeyboardListItem} Empty={NoKeyboardsFound} itemProps={this.props} />
|
|
||||||
<Button onClick={() => getKeyboards({ force: true })}>Refresh</Button>
|
|
||||||
{spinner && <Spinner />}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mapState = ({ keyboards, ui }) => ({
|
|
||||||
keyboards: keyboards.list,
|
|
||||||
spinner: ui.spinner
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapActions = {
|
|
||||||
getKeyboards,
|
|
||||||
showKeyboard
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapState,
|
|
||||||
mapActions
|
|
||||||
)(Keyboards);
|
|
||||||
@@ -15,11 +15,23 @@ import {
|
|||||||
} from "../redux/snake.js";
|
} from "../redux/snake.js";
|
||||||
|
|
||||||
/* Components */
|
/* Components */
|
||||||
import Title from "../components/Title.js";
|
import ControlPanel from "../components/ControlPanel.js";
|
||||||
import Button, { ToggleButton } from "../components/Button.js";
|
|
||||||
import Scoreboard from "../components/Scoreboard.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`
|
const Cell = styled.div`
|
||||||
width: ${({ theme }) => theme.snake.cell.size};
|
width: ${({ theme }) => theme.snake.cell.size};
|
||||||
@@ -35,7 +47,7 @@ const Cell = styled.div`
|
|||||||
const SnakePart = styled.div`
|
const SnakePart = styled.div`
|
||||||
width: ${({ theme }) => theme.snake.cell.size};
|
width: ${({ theme }) => theme.snake.cell.size};
|
||||||
height: ${({ 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;
|
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;
|
line-height: 1.8rem;
|
||||||
padding-left: 0.0625rem;
|
padding-left: 0.0625rem;
|
||||||
&:after {
|
&:after {
|
||||||
content: "${props => (!props.died ? "🍎" : "")}";
|
content: "${props => (!props.died ? "🍎" : "🐛")}";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -60,44 +72,30 @@ const Skull = styled.div.attrs({ role: "img", "aria-label": "skull" })`
|
|||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
line-height: 1.8rem;
|
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;
|
padding-left: 0.0625rem;
|
||||||
box-shadow: 1px 1px 2px #ddd inset, -1px -1px 2px gray inset;
|
box-shadow: 1px 1px 2px #ddd inset, -1px -1px 2px gray inset;
|
||||||
&:after {
|
&:after {
|
||||||
content: "💀";
|
content: "${props => props.value && props.value.index === 0 && "💀"}";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Grid = ({ data, died }) =>
|
const Grid = ({ data, died }) => (
|
||||||
data.map((r, y) => (
|
<div>
|
||||||
<Row key={`y${y}`}>
|
{data.map((r, y) => (
|
||||||
{r.map((c, x) => (
|
<Row key={`y${y}`}>
|
||||||
<Cell key={`x${x}y${y}`} died={died}>
|
{r.map((c, x) => (
|
||||||
{c === "a" && <Apple died={died} />}
|
<Cell key={`x${x}y${y}`} died={died}>
|
||||||
{!died && c[0] === "s" && <SnakePart value={c} />}
|
{c.type === "apple" && <Apple died={died} />}
|
||||||
{died && c[0] === "s" && <Skull value={c} />}
|
{!died && c.type === "snake" && <SnakePart value={c} />}
|
||||||
</Cell>
|
{died && c.type === "snake" && <Skull value={c} />}
|
||||||
))}
|
</Cell>
|
||||||
</Row>
|
))}
|
||||||
));
|
</Row>
|
||||||
|
))}
|
||||||
// const ArrowButton = styled(Button)`
|
</div>
|
||||||
// min-width: 5rem;
|
);
|
||||||
// `;
|
|
||||||
|
|
||||||
// const ArrowCluster = () => (
|
|
||||||
// <div>
|
|
||||||
// <div>
|
|
||||||
// <ArrowButton>Up</ArrowButton>
|
|
||||||
// </div>
|
|
||||||
// <div>
|
|
||||||
// <ArrowButton>Left</ArrowButton>
|
|
||||||
// <ArrowButton>Down</ArrowButton>
|
|
||||||
// <ArrowButton>Right</ArrowButton>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
|
|
||||||
class Snake extends React.Component {
|
class Snake extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -133,15 +131,20 @@ class Snake extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Scoreboard score={score} zoom={2} />
|
<Layout>
|
||||||
<Grid data={grid} died={died} />
|
<Grid data={grid} died={died} />
|
||||||
<Button onClick={updateFrameSnake}>Next</Button>
|
<SidePanel>
|
||||||
<ToggleButton toggle={paused} onClick={pauseSnake}>
|
<Scoreboard score={score} zoom={2} />
|
||||||
{paused ? "Resume" : "Pause"}
|
<ControlPanel
|
||||||
</ToggleButton>
|
updateFrameSnake={updateFrameSnake}
|
||||||
<Button onClick={stopSnake}>Stop</Button>
|
paused={paused}
|
||||||
<Button onClick={startSnake}>Reset</Button>
|
pauseSnake={pauseSnake}
|
||||||
<span>FPS: {fps}</span>
|
stopSnake={stopSnake}
|
||||||
|
startSnake={startSnake}
|
||||||
|
fps={fps}
|
||||||
|
/>
|
||||||
|
</SidePanel>
|
||||||
|
</Layout>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
@@ -6,7 +6,7 @@ export const module = new Module(MODULE_NAME, {
|
|||||||
loopId: null,
|
loopId: null,
|
||||||
callers: [],
|
callers: [],
|
||||||
keyPressSubscribers: [],
|
keyPressSubscribers: [],
|
||||||
fps: 5
|
fps: 8
|
||||||
});
|
});
|
||||||
|
|
||||||
export const keys = {
|
export const keys = {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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();
|
|
||||||
};
|
|
||||||
@@ -17,6 +17,8 @@ const initialState = {
|
|||||||
paused: false,
|
paused: false,
|
||||||
vX: 1,
|
vX: 1,
|
||||||
vY: 0,
|
vY: 0,
|
||||||
|
vXNext: undefined,
|
||||||
|
vYNext: undefined,
|
||||||
snake: [],
|
snake: [],
|
||||||
score: 0,
|
score: 0,
|
||||||
apple: []
|
apple: []
|
||||||
@@ -42,7 +44,7 @@ export const keys = {
|
|||||||
k: "k",
|
k: "k",
|
||||||
l: "l",
|
l: "l",
|
||||||
|
|
||||||
p: "p",
|
snakePart: "snakePart",
|
||||||
s: "s",
|
s: "s",
|
||||||
r: "r"
|
r: "r"
|
||||||
};
|
};
|
||||||
@@ -52,6 +54,8 @@ function randomPosition(size) {
|
|||||||
}
|
}
|
||||||
const comparePositions = (pos1, pos2) => pos1[0] === pos2[0] && pos1[1] === pos2[1];
|
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) {
|
function createGrid(size) {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
for (let y = 0; y < size; y += 1) {
|
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 apple = options.started ? randomPosition(state.size) : initialState.apple;
|
||||||
const snake = options.started ? [[0, 0]] : initialState.snake;
|
const snake = options.started ? [[0, 0]] : initialState.snake;
|
||||||
|
|
||||||
markCell(grid, apple, "a");
|
markCell(grid, apple, { type: "apple" });
|
||||||
snake.forEach(p => markCell(grid, p, "s"));
|
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 };
|
return { ...state, ...initialState, grid, snake, apple, ...options };
|
||||||
});
|
});
|
||||||
|
|
||||||
module.reducer(CHANGE_DIRECTION, (state, { direction }) => {
|
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) {
|
if (direction === directions.UP && vY !== 1) {
|
||||||
return { ...state, vX: 0, vY: -1 };
|
return { ...state, vXNext: 0, vYNext: -1 };
|
||||||
}
|
}
|
||||||
if (direction === directions.DOWN && vY !== -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) {
|
if (direction === directions.LEFT && vX !== 1) {
|
||||||
return { ...state, vX: -1, vY: 0 };
|
return { ...state, vXNext: -1, vYNext: 0 };
|
||||||
}
|
}
|
||||||
if (direction === directions.RIGHT && vX !== -1) {
|
if (direction === directions.RIGHT && vX !== -1) {
|
||||||
return { ...state, vX: 1, vY: 0 };
|
return { ...state, vXNext: 1, vYNext: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
|
|
||||||
module.reducer(UPDATE_FRAME_SNAKE, 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;
|
let { apple, started, died, score } = state;
|
||||||
const grid = createGrid(size);
|
const grid = createGrid(size);
|
||||||
const newSnake = [...snake];
|
const nextPosition = moveSnakePart(
|
||||||
const nextPosition = moveSnakePart(newSnake[newSnake.length - 1], vX, vY, size);
|
snake[snake.length - 1],
|
||||||
if (comparePositions(nextPosition, apple)) {
|
vXNext !== undefined ? vXNext : vX,
|
||||||
apple = randomPosition(size);
|
vYNext !== undefined ? vYNext : vY,
|
||||||
// eslint-disable-next-line no-loop-func
|
size
|
||||||
while (newSnake.filter(p => comparePositions(p, apple)).length) {
|
);
|
||||||
apple = randomPosition(size);
|
const newSnake = [...snake, nextPosition];
|
||||||
}
|
|
||||||
} else {
|
/* If the snake did not eat an apple then remove the last part of its tail */
|
||||||
|
if (!comparePositions(nextPosition, apple)) {
|
||||||
newSnake.shift();
|
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;
|
started = false;
|
||||||
died = true;
|
died = true;
|
||||||
}
|
}
|
||||||
newSnake.push(nextPosition);
|
|
||||||
markCell(grid, apple, "a");
|
if (comparePositions(nextPosition, apple)) {
|
||||||
const brightness = (l, i) => 20 + Math.round(0.8 * ((i + 1) / l) * 100);
|
apple = randomPosition(size);
|
||||||
newSnake.forEach((p, i) => markCell(grid, p, "s" + brightness(newSnake.length, i)));
|
// 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;
|
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 =============================================================================== */
|
/* === Middleware =============================================================================== */
|
||||||
@@ -217,7 +253,7 @@ module.middleware(KEY_PRESSED_SNAKE, (dispatch, _, { key }) => {
|
|||||||
if (key === keys.s) {
|
if (key === keys.s) {
|
||||||
dispatch(stopSnake());
|
dispatch(stopSnake());
|
||||||
}
|
}
|
||||||
if (key === keys.p) {
|
if (key === keys.snakePart) {
|
||||||
dispatch(pauseSnake());
|
dispatch(pauseSnake());
|
||||||
}
|
}
|
||||||
if (key === keys.r) {
|
if (key === keys.r) {
|
||||||
|
|||||||
51
src/redux/store.js
Normal file
51
src/redux/store.js
Normal file
@@ -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;
|
||||||
Reference in New Issue
Block a user