Update about page and documentation. Add allow wrapping option.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
export const Checkbox = styled.input.attrs({ type: "checkbox" })`
|
||||
appearance: none;
|
||||
|
||||
border-width: ${({ theme }) => theme.checkbox.borderWidth};
|
||||
border-style: solid;
|
||||
border-radius: ${({ theme }) => theme.checkbox.borderRadius};
|
||||
border-color: ${({ theme }) => theme.checkbox.borderColor};
|
||||
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
|
||||
position: relative;
|
||||
|
||||
&:checked {
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
display: block;
|
||||
top: 0;
|
||||
transform: rotate(-45deg);
|
||||
border-bottom: 1px solid black;
|
||||
border-left: 1px solid black;
|
||||
border-width: 3px;
|
||||
border-color: ${({ theme }) => theme.checkbox.checked.background};
|
||||
width: 10px;
|
||||
height: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: ${({ disabled }) => (!disabled ? "1px solid silver" : "none")};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.button.hover.background};
|
||||
}
|
||||
|
||||
&:active,
|
||||
&.active {
|
||||
border-color: ${({ theme }) => theme.button.active.borderColor};
|
||||
color: ${({ theme }) => theme.button.active.color};
|
||||
background-color: ${({ theme }) => theme.button.active.background};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Checkbox;
|
||||
@@ -2,10 +2,12 @@ import styled from "styled-components";
|
||||
|
||||
export const Code = styled.code`
|
||||
display: inline-block;
|
||||
color: white;
|
||||
background-color: gray;
|
||||
padding: 0 0.2rem;
|
||||
line-height: 1.4rem;
|
||||
font-weight: bold;
|
||||
background-color: ${({ theme }) => theme.button.background};
|
||||
color: ${({ theme }) => theme.button.color};
|
||||
padding: 0 0.3rem;
|
||||
line-height: 1.5rem;
|
||||
border-radius: 0.125rem;
|
||||
`;
|
||||
|
||||
export default Code;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { IconButton, ToggleIconButton } from "../components/Button.js";
|
||||
import Link from "../components/Link.js";
|
||||
import Checkbox from "../components/Checkbox.js";
|
||||
|
||||
const buttonSize = 2.5;
|
||||
|
||||
@@ -33,9 +35,26 @@ export const ControlPanel = ({
|
||||
zoom,
|
||||
setFps,
|
||||
zoomIn,
|
||||
zoomOut
|
||||
zoomOut,
|
||||
allowWrapping,
|
||||
changeAllowWrapping
|
||||
}) => (
|
||||
<Layout>
|
||||
<HorizontalStack style={{ minHeight: "2rem", lineHeight: "2rem" }}>
|
||||
<Link smooth to="/about#how-to-play">
|
||||
How to play?
|
||||
</Link>
|
||||
</HorizontalStack>
|
||||
<HorizontalStack style={{ minHeight: "2rem", lineHeight: "2rem" }}>
|
||||
<label htmlFor="allow-wrapping">Allow wrapping:</label>
|
||||
<div style={{ display: "inline-block" }}>
|
||||
<Checkbox
|
||||
id="allow-wrapping"
|
||||
checked={allowWrapping}
|
||||
onChange={({ target: { checked } }) => changeAllowWrapping(checked)}
|
||||
/>
|
||||
</div>
|
||||
</HorizontalStack>
|
||||
<HorizontalStack style={{ minHeight: "2rem", lineHeight: "2rem" }}>
|
||||
<span>
|
||||
Zoom: <b>{zoom}</b>
|
||||
|
||||
@@ -48,7 +48,6 @@ const HighscoreEntry = styled.li`
|
||||
`;
|
||||
|
||||
const Highscore = ({ highscores = [] }) => {
|
||||
console.log("highscores", highscores);
|
||||
return (
|
||||
<HighscoreContainer>
|
||||
<Title>Highscore</Title>
|
||||
|
||||
+13
-1
@@ -1,5 +1,6 @@
|
||||
import styled from "styled-components";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { HashLink } from "react-router-hash-link";
|
||||
|
||||
const SharedStyle = () => `
|
||||
color: gray;
|
||||
@@ -19,6 +20,13 @@ const StyledNavLink = styled(NavLink)`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledHashLink = styled(HashLink)`
|
||||
${SharedStyle}
|
||||
&.active {
|
||||
color: black;
|
||||
}
|
||||
`;
|
||||
|
||||
const Link = (props, refs) => {
|
||||
const updatedProps = { ...props };
|
||||
|
||||
@@ -26,7 +34,11 @@ const Link = (props, refs) => {
|
||||
updatedProps.onClick = e => e.preventDefault() || props.onClick();
|
||||
}
|
||||
|
||||
return props.to ? StyledNavLink.render(updatedProps) : StyledLink.render(updatedProps);
|
||||
return props.to
|
||||
? props.to.match("#")
|
||||
? StyledHashLink.render(updatedProps)
|
||||
: StyledNavLink.render(updatedProps)
|
||||
: StyledLink.render(updatedProps);
|
||||
};
|
||||
|
||||
export default Link;
|
||||
|
||||
@@ -6,8 +6,11 @@ export const Content = styled.div`
|
||||
content === PageSection.content.center &&
|
||||
css`
|
||||
max-width: 60rem;
|
||||
min-height: 31.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
// padding: 0.5em;
|
||||
`}
|
||||
`;
|
||||
|
||||
@@ -16,10 +19,12 @@ const contentPosition = {
|
||||
stretch: "stretch"
|
||||
};
|
||||
|
||||
export const PageSection = ({ content, children, className } = { content: contentPosition.stretch }) => {
|
||||
export const PageSection = ({ content, children, className, center, stretch } = {}) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Content content={content}>{children}</Content>
|
||||
<Content content={content || (center && contentPosition.center) || (stretch && contentPosition.stretch)}>
|
||||
<div>{children}</div>
|
||||
</Content>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,12 +22,12 @@ const Cell = styled.div.attrs(({ theme, zoom }) => ({
|
||||
box-shadow: ${({ theme }) => theme.stage.cell.boxShadow};
|
||||
`;
|
||||
|
||||
export const Stage = ({ data, children, zoom = 1 }) => (
|
||||
export const Stage = ({ grid, children, zoom = 1 }) => (
|
||||
<div>
|
||||
{data.map((r, y) => (
|
||||
<Row key={`y${y}`} zoom={zoom}>
|
||||
{grid.map((r, y) => (
|
||||
<Row key={`y${y}`} {...{ zoom }}>
|
||||
{r.map((c, x) => (
|
||||
<Cell key={`x${x}y${y}`} zoom={zoom}>
|
||||
<Cell key={`x${x}y${y}`} {...{ zoom }}>
|
||||
{children(c, zoom)}
|
||||
</Cell>
|
||||
))}
|
||||
|
||||
@@ -38,6 +38,7 @@ const ThemeSelector = ({ theme, changeTheme, border }) => {
|
||||
<StyledSelect border={border} onChange={e => changeTheme(e.target.value)} value={theme}>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Miami Night</option>
|
||||
<option value="forestNight">Forest Night</option>
|
||||
<option value="darkOcean">Dark Ocean</option>
|
||||
<option value="default">Black / White</option>
|
||||
</StyledSelect>
|
||||
|
||||
+10
-1
@@ -1,10 +1,19 @@
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
const Title = styled.h1`
|
||||
const Title = styled.h1.attrs(({ id }) => ({
|
||||
id
|
||||
}))`
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
line-height: 4rem;
|
||||
|
||||
${props =>
|
||||
props.smaller &&
|
||||
css`
|
||||
font-size: 1.2rem;
|
||||
line-height: 3rem;
|
||||
`}
|
||||
|
||||
${props =>
|
||||
props.large &&
|
||||
css`
|
||||
|
||||
+205
-34
@@ -3,6 +3,8 @@ import styled, { css } from "styled-components";
|
||||
import Title from "../components/Title.js";
|
||||
import PageSection from "../components/PageSection.js";
|
||||
import Code from "../components/Code.js";
|
||||
import Button from "../components/Button.js";
|
||||
import Link from "../components/Link.js";
|
||||
|
||||
const MainPageSection = styled(PageSection)`
|
||||
background-color: ${({ theme }) => theme.colors.pageSectionMainColor};
|
||||
@@ -54,9 +56,32 @@ const Layout = styled.div`
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledAboutPage = styled.div`
|
||||
font-size: 1.5rem;
|
||||
`;
|
||||
|
||||
const KeyMapTable = styled.table`
|
||||
margin: 3rem 0;
|
||||
thead td {
|
||||
font-size: 0.7em;
|
||||
color: ${({ theme }) => theme.colors.colorAlternate};
|
||||
}
|
||||
|
||||
td:nth-child(1),
|
||||
td:nth-child(2) {
|
||||
padding: 0 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
td:nth-child(3) {
|
||||
padding: 0 1rem;
|
||||
line-height: 2.3rem;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
`;
|
||||
|
||||
export const About = () => (
|
||||
<React.Fragment>
|
||||
<MainPageSection content={PageSection.content.center}>
|
||||
<StyledAboutPage>
|
||||
<MainPageSection center>
|
||||
<Layout style={{ justifyContent: "center" }} direction={"horizontal"}>
|
||||
<Layout style={{ width: "30rem", placeContent: "center" }} direction={"vertical"}>
|
||||
<Title larger>Crafity Snake</Title>
|
||||
@@ -65,58 +90,204 @@ export const About = () => (
|
||||
<Icon>🐍</Icon>
|
||||
</Layout>
|
||||
</MainPageSection>
|
||||
<StandardPageSection content={PageSection.content.center}>
|
||||
<StandardPageSection center>
|
||||
<Title>Introduction</Title>
|
||||
<p>
|
||||
This experiment has been created to test out React, Redux and Styled Components and see if it is possible to
|
||||
create a simple web based game like snake. The source code for this game is open source and free to download and
|
||||
change.
|
||||
This game has been created as a <b>Crafity</b> experiment to test out React, Redux and Styled Components and see
|
||||
if it is possible to create a simple web based game like snake. The{" "}
|
||||
<Link smooth to="/about#source-code">
|
||||
source code
|
||||
</Link>{" "}
|
||||
for this game is open source and free to download and change.
|
||||
</p>
|
||||
<p>
|
||||
A couple of noticable features in this game are resumable game play using local storage. The page can be
|
||||
A couple of noticeable features in this game are resumable game play using local storage. The page can be
|
||||
reloaded or reopened any time and the game should continue from where it was left.
|
||||
</p>
|
||||
</StandardPageSection>
|
||||
<AlternatePageSection content={PageSection.content.center}>
|
||||
<Title>How to play</Title>
|
||||
<AlternatePageSection center>
|
||||
<Title id="how-to-play">How to play</Title>
|
||||
<p>
|
||||
You control the snake using the arrow keys or the <Code>hjkl</Code> keys. Everytime the snake eats an apple the
|
||||
tail of the snake grows longer. When the snake touches his own body the game is over.
|
||||
In this game you play a snake and the goal is to eat as many apples as possible. Every time your snake eats an
|
||||
apple its tail grows a little bit longer and you earn a point. If your snake touches its own tail or one of the
|
||||
four walls* it will die.
|
||||
</p>
|
||||
<p>
|
||||
Other keys to control the game are <Code>r</Code> to (re)start the game. The <Code>s</Code> key to stop the game
|
||||
and <Code>p</Code> to pause the game.
|
||||
<i>* Only when allow wrapping is turned off</i>
|
||||
</p>
|
||||
<KeyMapTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Key</td>
|
||||
<td>Alternative</td>
|
||||
<td />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>r</Code>
|
||||
</td>
|
||||
<td />
|
||||
<td>(Re)start the game</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>s</Code>
|
||||
</td>
|
||||
<td />
|
||||
<td>Stop the game</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>p</Code>
|
||||
</td>
|
||||
<td />
|
||||
<td>Pause the game</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>h</Code>
|
||||
</td>
|
||||
<td>
|
||||
<Code>←</Code>
|
||||
</td>
|
||||
<td>Left</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>j</Code>
|
||||
</td>
|
||||
<td>
|
||||
<Code>↓</Code>
|
||||
</td>
|
||||
<td>Down</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>k</Code>
|
||||
</td>
|
||||
<td>
|
||||
<Code>↑</Code>
|
||||
</td>
|
||||
<td>Up</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>l</Code>
|
||||
</td>
|
||||
<td>
|
||||
<Code>→</Code>
|
||||
</td>
|
||||
<td>Right</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>w</Code>
|
||||
</td>
|
||||
<td>
|
||||
<Code />
|
||||
</td>
|
||||
<td>Allow Wrapping</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>+</Code>
|
||||
</td>
|
||||
<td>
|
||||
<Code>=</Code>
|
||||
</td>
|
||||
<td>Increase FPS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>-</Code>
|
||||
</td>
|
||||
<td />
|
||||
<td>Decrease FPS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Code>0...9</Code>
|
||||
</td>
|
||||
<td />
|
||||
<td>Zoom level</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</KeyMapTable>
|
||||
<p style={{ textAlign: "center", margin: "2rem 0" }}>
|
||||
<Button
|
||||
style={{ width: "10rem", height: "3rem", lineHeight: "3rem", fontWeight: "bold", fontSize: "1.5rem" }}
|
||||
exact
|
||||
to="/">
|
||||
Play now
|
||||
</Button>
|
||||
</p>
|
||||
</AlternatePageSection>
|
||||
<StandardPageSection content={PageSection.content.center}>
|
||||
<StandardPageSection center>
|
||||
<Title>Technology</Title>
|
||||
<p>Snake has been developed with the folowing technologies.</p>
|
||||
<ul>
|
||||
<li>React + React Router</li>
|
||||
<li>Redux</li>
|
||||
<li>Styled Components</li>
|
||||
<li>Webpack + Babel</li>
|
||||
<li>Jest</li>
|
||||
<li>ESLint</li>
|
||||
<li>pre-commit</li>
|
||||
<li>Docker</li>
|
||||
</ul>
|
||||
<Layout style={{ justifyContent: "space-around" }}>
|
||||
<ul>
|
||||
<li>React</li>
|
||||
<li>React Router</li>
|
||||
<li>Redux</li>
|
||||
<li>Styled Components</li>
|
||||
<li>Webpack</li>
|
||||
<li>Babel</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>Font Awesome</li>
|
||||
<li>Jest</li>
|
||||
<li>ESLint</li>
|
||||
<li>pre-commit</li>
|
||||
<li>Docker</li>
|
||||
</ul>
|
||||
</Layout>
|
||||
<Layout style={{ justifyContent: "space-around", height: "64px", marginBottom: "1rem" }}>
|
||||
<img src="img/reactjs.png" title="React" alt="React Logo" />
|
||||
<img src="img/reduxjs.png" title="Redux" alt="Redux Logo" />
|
||||
<img src="img/styled-components.png" title="Styled Components" alt="Styled Components Logo" />
|
||||
<img src="img/font-awesome.png" title="Font Awesome" alt="Font Awesome Logo" />
|
||||
<img src="img/webpack.png" title="Webpack" alt="Webpack Logo" />
|
||||
<img src="img/jest.png" title="Jest" alt="Jest Logo" />
|
||||
<img src="img/docker.png" title="Docker" alt="Docker Logo" />
|
||||
</Layout>
|
||||
<Title smaller>State Management</Title>
|
||||
<p>
|
||||
All the game and UI state is managed with Redux. This means all the state is stored in a central state store.
|
||||
Every second the state is serialized and stored in the browser"s localStorage. This makes the game fully
|
||||
resumable.
|
||||
</p>
|
||||
<p>Change is changed using Redux reducers and the orchistration of actions is handled by Redux Middleware.</p>
|
||||
<Title smaller>Styling</Title>
|
||||
<p>
|
||||
All the styling and theming is handled by the module styled-components. Styled-components let"s you style
|
||||
your react components directly from your Javascript code.
|
||||
</p>
|
||||
</StandardPageSection>
|
||||
<AlternatePageSection content={PageSection.content.center}>
|
||||
<Title>Source Code</Title>
|
||||
<p>The source code is hosted on Crafity's git repositories at the following location:</p>
|
||||
<AlternatePageSection center>
|
||||
<Title id="source-code">Source Code</Title>
|
||||
<p>The source code is hosted on Crafity's Gitea environment at the following location:</p>
|
||||
<p>
|
||||
<a href="https://git.crafity.com">Crafity Snake</a>
|
||||
</p>
|
||||
<p>After donwloading the source code run the following commands:</p>
|
||||
<p>
|
||||
<Code>npm install</Code>
|
||||
<Link target="_blank" href="https://git.crafity.com/Crafity/snake">
|
||||
Download here
|
||||
</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Code>npm run dev</Code>
|
||||
The source code is MIT licensed. Feel free to make modifications and share. Suggestions and pull requests are
|
||||
welcome. Use Crafity"s git repository to file issues and pull requests.
|
||||
</p>
|
||||
</AlternatePageSection>
|
||||
</React.Fragment>
|
||||
<StandardPageSection center>
|
||||
<Title>Running Snake</Title>
|
||||
<p>After donwloading the source code run the following commands:</p>
|
||||
<Code style={{ display: "block", padding: "1rem" }}>
|
||||
<p>$ npm install</p>
|
||||
<p>$ npm run dev</p>
|
||||
</Code>
|
||||
</StandardPageSection>
|
||||
</StyledAboutPage>
|
||||
);
|
||||
|
||||
export default About;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
export const Empty = () => {
|
||||
return <h1>lskdfj</h1>;
|
||||
};
|
||||
|
||||
export default Empty;
|
||||
+28
-20
@@ -12,6 +12,7 @@ import {
|
||||
pauseSnake,
|
||||
updateFrameSnake,
|
||||
keyPressedSnake,
|
||||
changeAllowWrapping,
|
||||
zoomIn,
|
||||
zoomOut
|
||||
} from "../redux/snake.js";
|
||||
@@ -68,7 +69,9 @@ const Snake = ({
|
||||
lastGameId,
|
||||
zoom,
|
||||
zoomIn,
|
||||
zoomOut
|
||||
zoomOut,
|
||||
allowWrapping,
|
||||
changeAllowWrapping
|
||||
}) => {
|
||||
const onSubmitHighscore = result => {
|
||||
if (result) {
|
||||
@@ -82,10 +85,10 @@ const Snake = ({
|
||||
<React.Fragment>
|
||||
<Layout>
|
||||
<StageContainer>
|
||||
<Stage data={grid} zoom={zoom}>
|
||||
<Stage {...{ zoom, grid }}>
|
||||
{cell =>
|
||||
(cell.type === "apple" && <Apple zoom={zoom} died={died} />) ||
|
||||
(cell.type === "snake" && <SnakePart zoom={zoom} value={cell} died={died} />)
|
||||
(cell.type === "apple" && <Apple {...{ zoom, died }} />) ||
|
||||
(cell.type === "snake" && <SnakePart value={cell} {...{ zoom, died }} />)
|
||||
}
|
||||
</Stage>
|
||||
{!started && (
|
||||
@@ -101,26 +104,30 @@ const Snake = ({
|
||||
</Banner>
|
||||
)}
|
||||
{died && lastGameId !== gameId && hasHighscore(score) && (
|
||||
<HighscoreInput score={score} gameId={gameId} name={lastUsername} onSubmit={onSubmitHighscore} />
|
||||
<HighscoreInput name={lastUsername} onSubmit={onSubmitHighscore} {...{ score, gameId }} />
|
||||
)}
|
||||
</StageContainer>
|
||||
<SidePanel>
|
||||
<Scoreboard score={score} zoom={3} />
|
||||
<Scoreboard {...{ score }} zoom={3} />
|
||||
<Highscore />
|
||||
<ControlPanel
|
||||
updateFrameSnake={updateFrameSnake}
|
||||
started={started}
|
||||
paused={paused}
|
||||
pauseSnake={pauseSnake}
|
||||
stopSnake={stopSnake}
|
||||
startSnake={startSnake}
|
||||
theme={theme}
|
||||
changeTheme={changeTheme}
|
||||
fps={fps}
|
||||
setFps={setFps}
|
||||
zoom={zoom}
|
||||
zoomIn={zoomIn}
|
||||
zoomOut={zoomOut}
|
||||
{...{
|
||||
updateFrameSnake,
|
||||
started,
|
||||
paused,
|
||||
pauseSnake,
|
||||
stopSnake,
|
||||
startSnake,
|
||||
theme,
|
||||
changeTheme,
|
||||
fps,
|
||||
setFps,
|
||||
zoom,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
allowWrapping,
|
||||
changeAllowWrapping
|
||||
}}
|
||||
/>
|
||||
</SidePanel>
|
||||
</Layout>
|
||||
@@ -148,7 +155,8 @@ const mapActionsToProps = {
|
||||
zoomOut,
|
||||
changeTheme,
|
||||
registerHighscore,
|
||||
skipHighscore
|
||||
skipHighscore,
|
||||
changeAllowWrapping
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -16,6 +16,7 @@ export const keys = {
|
||||
p: "p",
|
||||
s: "s",
|
||||
r: "r",
|
||||
w: "w",
|
||||
|
||||
1: "1",
|
||||
2: "2",
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { Provider, ReactReduxContext } from "react-redux";
|
||||
import { ConnectedRouter } from "connected-react-router";
|
||||
|
||||
import { createStore } from "./redux/store";
|
||||
@@ -13,8 +13,8 @@ const store = createStore();
|
||||
window.store = store;
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={store.history}>
|
||||
<Provider store={store} context={ReactReduxContext}>
|
||||
<ConnectedRouter history={store.history} context={ReactReduxContext}>
|
||||
<App />
|
||||
</ConnectedRouter>
|
||||
</Provider>,
|
||||
|
||||
@@ -214,7 +214,6 @@ export class Module {
|
||||
|
||||
select(...items) {
|
||||
return (items || []).reduce((selection, item) => {
|
||||
console.log("item", item);
|
||||
return selection;
|
||||
}, {});
|
||||
}
|
||||
|
||||
+56
-17
@@ -10,6 +10,7 @@ const DEFAUL_ZOOM_STEP = 0.1;
|
||||
const initialState = {
|
||||
grid: createGrid(DEFAULT_GRID_SIZE),
|
||||
zoom: 1.5,
|
||||
allowWrapping: false,
|
||||
size: DEFAULT_GRID_SIZE,
|
||||
paused: false,
|
||||
gameId: 0,
|
||||
@@ -64,9 +65,12 @@ const updateGrid = ({ grid, apple, snake }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const moveSnakePart = ([x, y], vX, vY, size) => {
|
||||
const moveSnakePart = ([x, y], vX, vY, size, allowWrapping) => {
|
||||
let newX = x + vX;
|
||||
let newY = y + vY;
|
||||
if (!allowWrapping) {
|
||||
return [newX, newY];
|
||||
}
|
||||
if (newX > size - 1) {
|
||||
newX = 0;
|
||||
}
|
||||
@@ -82,6 +86,22 @@ const moveSnakePart = ([x, y], vX, vY, size) => {
|
||||
return [newX, newY];
|
||||
};
|
||||
|
||||
const isOutOfBound = ([x, y], size) => {
|
||||
if (x > size - 1) {
|
||||
return true;
|
||||
}
|
||||
if (x < 0) {
|
||||
return true;
|
||||
}
|
||||
if (y > size - 1) {
|
||||
return true;
|
||||
}
|
||||
if (y < 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const module = new Module(MODULE_NAME, initialState);
|
||||
|
||||
/* === Actions ================================================================================== */
|
||||
@@ -97,6 +117,11 @@ export const [ZOOM_OUT, zoomOut] = module.action("ZOOM_OUT", step => ({ step }))
|
||||
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 [CHANGE_ALLOW_WRAPPING, changeAllowWrapping] = module.action("CHANGE_ALLOW_WRAPPING", allowWrapping => ({
|
||||
allowWrapping
|
||||
}));
|
||||
export const [TOGGLE_ALLOW_WRAPPING, toggleAllowWrapping] = module.action("TOGGLE_ALLOW_WRAPPING");
|
||||
|
||||
export const [UPDATE_STATE, updateState] = module.action("UPDATE_STATE", (newState, fullUpdate = false) => ({
|
||||
newState,
|
||||
fullUpdate
|
||||
@@ -117,11 +142,12 @@ module.reducer(ZOOM_OUT, (state, { step = DEFAUL_ZOOM_STEP }) =>
|
||||
module.reducer(RESET_SNAKE, (state, options) => {
|
||||
const grid = createGrid(state.size);
|
||||
const zoom = state.zoom;
|
||||
const allowWrapping = state.allowWrapping;
|
||||
const gameId = (state.gameId || 0) + 1;
|
||||
const apple = options.started ? randomPosition(state.size) : initialState.apple;
|
||||
const snake = options.started ? [[0, 0]] : initialState.snake;
|
||||
updateGrid({ grid, apple, snake });
|
||||
return { ...state, ...initialState, grid, zoom, snake, apple, gameId, ...options };
|
||||
return { ...state, ...initialState, grid, allowWrapping, zoom, snake, apple, gameId, ...options };
|
||||
});
|
||||
|
||||
module.reducer(CHANGE_DIRECTION, (state, { direction }) => {
|
||||
@@ -145,31 +171,39 @@ module.reducer(CHANGE_DIRECTION, (state, { direction }) => {
|
||||
});
|
||||
|
||||
module.reducer(UPDATE_FRAME_SNAKE, state => {
|
||||
const { snake, vX, vY, next, size } = state;
|
||||
const { snake, vX, vY, next, size, allowWrapping } = state;
|
||||
let { apple, started, died, score } = state;
|
||||
const grid = createGrid(size);
|
||||
|
||||
const [vXNext, vYNext] = next.length ? next[0] : [vX, vY];
|
||||
|
||||
const nextPosition = moveSnakePart(snake[snake.length - 1], vXNext, vYNext, size);
|
||||
const newSnake = [...snake, nextPosition];
|
||||
const nextPosition = moveSnakePart(snake[snake.length - 1], vXNext, vYNext, size, allowWrapping);
|
||||
let newSnake;
|
||||
|
||||
/* If the snake did not eat an apple then remove the last part of its tail */
|
||||
if (!comparePositions(nextPosition, apple)) {
|
||||
newSnake.shift();
|
||||
}
|
||||
|
||||
/* Check if the snake hits itself */
|
||||
if (newSnake.slice(0, -1).filter(snakePart => comparePositions(snakePart, nextPosition)).length) {
|
||||
if (!allowWrapping && isOutOfBound(nextPosition, size)) {
|
||||
newSnake = snake;
|
||||
started = false;
|
||||
died = true;
|
||||
}
|
||||
} else {
|
||||
newSnake = [...snake, nextPosition];
|
||||
|
||||
if (comparePositions(nextPosition, apple)) {
|
||||
apple = randomPosition(size);
|
||||
// eslint-disable-next-line no-loop-func
|
||||
while (newSnake.filter(snakePart => comparePositions(snakePart, apple)).length) {
|
||||
/* If the snake did not eat an apple then remove the last part of its tail */
|
||||
if (!comparePositions(nextPosition, apple)) {
|
||||
newSnake.shift();
|
||||
}
|
||||
|
||||
/* Check if the snake hits itself */
|
||||
if (newSnake.slice(0, -1).filter(snakePart => comparePositions(snakePart, nextPosition)).length) {
|
||||
started = false;
|
||||
died = true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateGrid({ grid, apple, snake: newSnake });
|
||||
@@ -188,6 +222,8 @@ module.reducer(UPDATE_FRAME_SNAKE, state => {
|
||||
score
|
||||
};
|
||||
});
|
||||
module.reducer(CHANGE_ALLOW_WRAPPING, (state, { allowWrapping }) => ({ ...state, allowWrapping }));
|
||||
module.reducer(TOGGLE_ALLOW_WRAPPING, state => ({ ...state, allowWrapping: !state.allowWrapping }));
|
||||
|
||||
/* === Middleware =============================================================================== */
|
||||
|
||||
@@ -259,6 +295,9 @@ module.middleware(KEY_PRESSED_SNAKE, (dispatch, _, { key, altKey, ctrlKey, metaK
|
||||
if (key === keys.r) {
|
||||
dispatch(startSnake());
|
||||
}
|
||||
if (key === keys.w) {
|
||||
dispatch(toggleAllowWrapping());
|
||||
}
|
||||
|
||||
/* Map the number keys to a zoom level */
|
||||
const keyAsInt = parseInt(key, 10);
|
||||
|
||||
@@ -2,11 +2,13 @@ import defaultTheme from "./themes/default.js";
|
||||
import lightTheme from "./themes/light.js";
|
||||
import darkTheme from "./themes/dark.js";
|
||||
import darkOceanTheme from "./themes/darkOcean.js";
|
||||
import forestNightTheme from "./themes/forestNight.js";
|
||||
|
||||
const themes = {
|
||||
default: defaultTheme,
|
||||
light: lightTheme,
|
||||
dark: darkTheme,
|
||||
forestNight: forestNightTheme,
|
||||
darkOcean: darkOceanTheme
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ export const colors = {
|
||||
spinnerHighlight: "#db7093",
|
||||
cardFoldHighlight: "#ad5a75",
|
||||
cardFoldShadow: "#bdb19a",
|
||||
selectColor: "#db7093"
|
||||
selectColor: "#db7093",
|
||||
pageSectionMainColor: "#333",
|
||||
pageSectionStandardColor: "#444",
|
||||
pageSectionAlternateColor: "#555"
|
||||
};
|
||||
|
||||
const mapRange = (l, i, min, max) => Math.round((max - min) * ((l - i) / l) + min);
|
||||
|
||||
@@ -3,11 +3,11 @@ import { renderTheme } from "./default.js";
|
||||
|
||||
export const colors = {
|
||||
background: "#333",
|
||||
backgroundActive: "#828282",
|
||||
backgroundActive: "transparent",
|
||||
backgroundInactive: "#4d4d4d",
|
||||
backgroundAlternate: "#555",
|
||||
color: "#7094db",
|
||||
colorActive: "#ecb1c5",
|
||||
colorActive: "#7094db",
|
||||
colorInactive: "#7d98d4",
|
||||
colorAlternate: "#a1c0fc",
|
||||
shadowColor: "#222",
|
||||
@@ -18,7 +18,10 @@ export const colors = {
|
||||
spinnerHighlight: "#db7093",
|
||||
cardFoldHighlight: "#091225",
|
||||
cardFoldShadow: "#bdb19a",
|
||||
selectColor: "#7094db"
|
||||
selectColor: "#7094db",
|
||||
pageSectionMainColor: "#333",
|
||||
pageSectionStandardColor: "#444",
|
||||
pageSectionAlternateColor: "#555"
|
||||
};
|
||||
|
||||
const mapRange = (l, i, min, max) => Math.round((max - min) * ((l - i) / l) + min);
|
||||
|
||||
@@ -64,6 +64,14 @@ export const renderTheme = themeColors => {
|
||||
}
|
||||
}
|
||||
},
|
||||
checkbox: {
|
||||
borderWidth: `${2 / 16}rem`,
|
||||
borderRadius: `${3 / 16}rem`,
|
||||
borderColor: colors.color,
|
||||
checked: {
|
||||
background: colors.colorAlternate
|
||||
}
|
||||
},
|
||||
button: {
|
||||
borderColor: colors.borderColor,
|
||||
background: colors.color,
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import merge from "deepmerge";
|
||||
import { renderTheme } from "./default.js";
|
||||
|
||||
export const colors = {
|
||||
background: "#333",
|
||||
backgroundActive: "#828282",
|
||||
backgroundInactive: "#4d4d4d",
|
||||
backgroundAlternate: "#555",
|
||||
// color: "#84db70",
|
||||
color: "#74EA4D",
|
||||
colorActive: "#ecb1c5",
|
||||
colorInactive: "#addda2",
|
||||
colorAlternate: "#55af41",
|
||||
shadowColor: "#222",
|
||||
borderColor: "#222",
|
||||
borderColorActive: "silver",
|
||||
borderColorInactive: "#addda2",
|
||||
spinnerShadow: "#444",
|
||||
spinnerHighlight: "#db7093",
|
||||
cardFoldHighlight: "#091225",
|
||||
cardFoldShadow: "#bdb19a",
|
||||
selectColor: "#addda2",
|
||||
pageSectionMainColor: "#333",
|
||||
pageSectionStandardColor: "#444",
|
||||
pageSectionAlternateColor: "#555"
|
||||
};
|
||||
|
||||
const mapRange = (l, i, min, max) => Math.round((max - min) * ((l - i) / l) + min);
|
||||
|
||||
export const darkTheme = merge(renderTheme(colors), {
|
||||
snakePart: {
|
||||
boxShadow: "1px 1px 2px #888 inset, -1px -1px 2px #222 inset",
|
||||
getColor(length, index, died) {
|
||||
const hue = 109;
|
||||
if (!died) {
|
||||
const saturation = mapRange(length, index, 20, 65);
|
||||
const brightness = 61;
|
||||
return `hsl(${hue}, ${saturation}%, ${brightness}%)`;
|
||||
}
|
||||
const saturation = 0;
|
||||
const brightness = mapRange(length, index, 70, 0);
|
||||
return `hsl(${hue}, ${saturation}%, ${brightness}%)`;
|
||||
}
|
||||
},
|
||||
stage: {
|
||||
cell: {
|
||||
boxShadow: "1px 1px 3px #191919 inset, -1px -1px 1px #777777 inset"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default darkTheme;
|
||||
Reference in New Issue
Block a user