diff --git a/README.md b/README.md index bba2fee..89d814c 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,34 @@ Play snake in your browser with HTML 5. This game is developed using React with hooks and Redux. All the game state and highscores are stored in local storage. +## Introduction + +This game has been created as a Crafity 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 for this game is open source and free to download and change. + +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. + ## Feature Technologies -- React + React Router +- React +- React Router - Redux - Styled Components -- Webpack + Babel +- Webpack +- Babel +- Font Awesome - Jest - ESLint - pre-commit +- Docker -## Todo +## TODO -[ ] Create welcome / instructions page / about page -[ ] Link to repository -[x] Add license -[x] High score -[ ] Docker build script -[ ] Improve banner / popup windows +[ ] Make stage wrapping an optional feature [ ] Touch and mobile support ## License +``` The MIT License (MIT) Copyright (c) Crafity VOF (crafity.com) @@ -45,3 +51,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` diff --git a/config/default.conf b/config/default.conf index d2e2982..4cb9ad2 100644 --- a/config/default.conf +++ b/config/default.conf @@ -5,8 +5,15 @@ server { #charset koi8-r; #access_log /var/log/nginx/host.access.log main; + root /usr/share/nginx/html; + + location /redux/img { + alias /usr/share/nginx/html/img; + } + + location /img {} + location / { - alias /usr/share/nginx/html/; try_files $uri /index.html; } diff --git a/package.json b/package.json index ef804ed..a1559cd 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "dev": "webpack-dev-server --mode development --open", + "dev": "webpack-dev-server --mode development", "build": "webpack --optimize-minimize --config webpack.production.config.js", "webpack": "webpack --config webpack.config.js --watch", "lint": "eslint src", @@ -52,6 +52,7 @@ "react-redux": "^7.1.0", "react-router": "^5.0.1", "react-router-dom": "^5.0.1", + "react-router-hash-link": "^1.2.2", "react-transition-group": "^4.2.2", "redux": "^4.0.4", "styled-components": "^4.3.2" diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..79ad120 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/img/docker.png b/public/img/docker.png new file mode 100644 index 0000000..8d8b093 Binary files /dev/null and b/public/img/docker.png differ diff --git a/public/img/font-awesome.png b/public/img/font-awesome.png new file mode 100644 index 0000000..739bb6d Binary files /dev/null and b/public/img/font-awesome.png differ diff --git a/public/img/jest.png b/public/img/jest.png new file mode 100644 index 0000000..43ac87e Binary files /dev/null and b/public/img/jest.png differ diff --git a/public/img/reactjs.png b/public/img/reactjs.png new file mode 100644 index 0000000..3339e57 Binary files /dev/null and b/public/img/reactjs.png differ diff --git a/public/img/reduxjs.png b/public/img/reduxjs.png new file mode 100644 index 0000000..3ae1e7a Binary files /dev/null and b/public/img/reduxjs.png differ diff --git a/public/img/snake.png b/public/img/snake.png new file mode 100644 index 0000000..f45ec09 Binary files /dev/null and b/public/img/snake.png differ diff --git a/public/img/styled-components.png b/public/img/styled-components.png new file mode 100644 index 0000000..9a42b76 Binary files /dev/null and b/public/img/styled-components.png differ diff --git a/public/img/webpack.png b/public/img/webpack.png new file mode 100644 index 0000000..cf3b9c0 Binary files /dev/null and b/public/img/webpack.png differ diff --git a/public/index.html b/public/index.html index 9776439..a58d43c 100644 --- a/public/index.html +++ b/public/index.html @@ -2,7 +2,7 @@ - Redux Demo + Crafity Snake - A React/Redux Demo + + + + + + +
diff --git a/src/components/Checkbox.js b/src/components/Checkbox.js new file mode 100644 index 0000000..ba3b4b9 --- /dev/null +++ b/src/components/Checkbox.js @@ -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; diff --git a/src/components/Code.js b/src/components/Code.js index 7127ebb..62a199f 100644 --- a/src/components/Code.js +++ b/src/components/Code.js @@ -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; diff --git a/src/components/ControlPanel.js b/src/components/ControlPanel.js index f7fbcee..5c179f5 100644 --- a/src/components/ControlPanel.js +++ b/src/components/ControlPanel.js @@ -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 }) => ( + + + How to play? + + + + +
+ changeAllowWrapping(checked)} + /> +
+
Zoom: {zoom} diff --git a/src/components/Highscore.js b/src/components/Highscore.js index d1fc7ab..38d05ec 100644 --- a/src/components/Highscore.js +++ b/src/components/Highscore.js @@ -48,7 +48,6 @@ const HighscoreEntry = styled.li` `; const Highscore = ({ highscores = [] }) => { - console.log("highscores", highscores); return ( Highscore diff --git a/src/components/Link.js b/src/components/Link.js index ada3712..c6df2d9 100644 --- a/src/components/Link.js +++ b/src/components/Link.js @@ -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; diff --git a/src/components/PageSection.js b/src/components/PageSection.js index a2c4777..6920c0d 100644 --- a/src/components/PageSection.js +++ b/src/components/PageSection.js @@ -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 (
- {children} + +
{children}
+
); }; diff --git a/src/components/Stage.js b/src/components/Stage.js index ef57f56..462aeb2 100644 --- a/src/components/Stage.js +++ b/src/components/Stage.js @@ -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 }) => (
- {data.map((r, y) => ( - + {grid.map((r, y) => ( + {r.map((c, x) => ( - + {children(c, zoom)} ))} diff --git a/src/components/ThemeSelector.js b/src/components/ThemeSelector.js index c0a31cd..b5ba5a9 100644 --- a/src/components/ThemeSelector.js +++ b/src/components/ThemeSelector.js @@ -38,6 +38,7 @@ const ThemeSelector = ({ theme, changeTheme, border }) => { changeTheme(e.target.value)} value={theme}> + diff --git a/src/components/Title.js b/src/components/Title.js index 080b804..d939cbb 100644 --- a/src/components/Title.js +++ b/src/components/Title.js @@ -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` diff --git a/src/containers/About.js b/src/containers/About.js index 67576cf..1d64a85 100644 --- a/src/containers/About.js +++ b/src/containers/About.js @@ -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 = () => ( - - + + Crafity Snake @@ -65,58 +90,204 @@ export const About = () => ( 🐍 - + Introduction

- 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 Crafity 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{" "} + + source code + {" "} + for this game is open source and free to download and change.

- 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.

- - How to play + + How to play

- You control the snake using the arrow keys or the hjkl 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.

- Other keys to control the game are r to (re)start the game. The s key to stop the game - and p to pause the game. + * Only when allow wrapping is turned off +

+ + + + Key + Alternative + + + + + + + r + + + (Re)start the game + + + + s + + + Stop the game + + + + p + + + Pause the game + + + + h + + + + + Left + + + + j + + + + + Down + + + + k + + + + + Up + + + + l + + + + + Right + + + + w + + + + + Allow Wrapping + + + + + + + + = + + Increase FPS + + + + - + + + Decrease FPS + + + + 0...9 + + + Zoom level + + + +

+

- + Technology

Snake has been developed with the folowing technologies.

-
    -
  • React + React Router
  • -
  • Redux
  • -
  • Styled Components
  • -
  • Webpack + Babel
  • -
  • Jest
  • -
  • ESLint
  • -
  • pre-commit
  • -
  • Docker
  • -
+ +
    +
  • React
  • +
  • React Router
  • +
  • Redux
  • +
  • Styled Components
  • +
  • Webpack
  • +
  • Babel
  • +
+
    +
  • Font Awesome
  • +
  • Jest
  • +
  • ESLint
  • +
  • pre-commit
  • +
  • Docker
  • +
+
+ + React Logo + Redux Logo + Styled Components Logo + Font Awesome Logo + Webpack Logo + Jest Logo + Docker Logo + + State Management +

+ 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. +

+

Change is changed using Redux reducers and the orchistration of actions is handled by Redux Middleware.

+ Styling +

+ 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. +

- - Source Code -

The source code is hosted on Crafity's git repositories at the following location:

+ + Source Code +

The source code is hosted on Crafity's Gitea environment at the following location:

- Crafity Snake -

-

After donwloading the source code run the following commands:

-

- npm install + + Download here +

- npm run dev + 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.

-
+ + Running Snake +

After donwloading the source code run the following commands:

+ +

$ npm install

+

$ npm run dev

+
+
+ ); export default About; diff --git a/src/containers/Empty.js b/src/containers/Empty.js new file mode 100644 index 0000000..45c51da --- /dev/null +++ b/src/containers/Empty.js @@ -0,0 +1,7 @@ +import React from "react"; + +export const Empty = () => { + return

lskdfj

; +}; + +export default Empty; diff --git a/src/containers/Snake.js b/src/containers/Snake.js index f81918b..7749a60 100644 --- a/src/containers/Snake.js +++ b/src/containers/Snake.js @@ -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 = ({ - + {cell => - (cell.type === "apple" && ) || - (cell.type === "snake" && ) + (cell.type === "apple" && ) || + (cell.type === "snake" && ) } {!started && ( @@ -101,26 +104,30 @@ const Snake = ({ )} {died && lastGameId !== gameId && hasHighscore(score) && ( - + )} - + @@ -148,7 +155,8 @@ const mapActionsToProps = { zoomOut, changeTheme, registerHighscore, - skipHighscore + skipHighscore, + changeAllowWrapping }; export default connect( diff --git a/src/enums/keys.js b/src/enums/keys.js index 44cb924..2f37b58 100644 --- a/src/enums/keys.js +++ b/src/enums/keys.js @@ -16,6 +16,7 @@ export const keys = { p: "p", s: "s", r: "r", + w: "w", 1: "1", 2: "2", diff --git a/src/index.js b/src/index.js index d57c564..db3fcdf 100644 --- a/src/index.js +++ b/src/index.js @@ -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( - - + + , diff --git a/src/redux/Module.js b/src/redux/Module.js index e94b916..4dd1c8c 100644 --- a/src/redux/Module.js +++ b/src/redux/Module.js @@ -214,7 +214,6 @@ export class Module { select(...items) { return (items || []).reduce((selection, item) => { - console.log("item", item); return selection; }, {}); } diff --git a/src/redux/snake.js b/src/redux/snake.js index c299a59..f1d970d 100644 --- a/src/redux/snake.js +++ b/src/redux/snake.js @@ -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); diff --git a/src/theming/theme.js b/src/theming/theme.js index f323323..c3074a2 100644 --- a/src/theming/theme.js +++ b/src/theming/theme.js @@ -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 }; diff --git a/src/theming/themes/dark.js b/src/theming/themes/dark.js index 8ec5499..6010945 100644 --- a/src/theming/themes/dark.js +++ b/src/theming/themes/dark.js @@ -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); diff --git a/src/theming/themes/darkOcean.js b/src/theming/themes/darkOcean.js index e0147f0..4a3517c 100644 --- a/src/theming/themes/darkOcean.js +++ b/src/theming/themes/darkOcean.js @@ -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); diff --git a/src/theming/themes/default.js b/src/theming/themes/default.js index f5b2a6c..e5362fb 100644 --- a/src/theming/themes/default.js +++ b/src/theming/themes/default.js @@ -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, diff --git a/src/theming/themes/forestNight.js b/src/theming/themes/forestNight.js new file mode 100644 index 0000000..11fbe94 --- /dev/null +++ b/src/theming/themes/forestNight.js @@ -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; diff --git a/webpack.config.js b/webpack.config.js index d4449ab..bc3c9c3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -17,6 +17,15 @@ module.exports = { port: 9000, historyApiFallback: true }, + externals: { + react: "React", + React: "React", + redux: "Redux", + "react-dom": "ReactDOM", + "react-redux": "ReactRedux", + "react-router": "ReactRouter" + // "react-router-dom": "ReactRouterDOM" + }, plugins: [new webpack.HotModuleReplacementPlugin()], module: { rules: [ diff --git a/webpack.production.config.js b/webpack.production.config.js index b7fc58e..dbb2f01 100644 --- a/webpack.production.config.js +++ b/webpack.production.config.js @@ -1,4 +1,3 @@ -const webpack = require("webpack"); const path = require("path"); module.exports = { @@ -6,10 +5,15 @@ module.exports = { mode: "production", entry: path.resolve(__dirname, "src/index.js"), output: { path: path.resolve(__dirname, "public") }, - plugins: [new webpack.HotModuleReplacementPlugin()], - devServer: { - hot: false, - inline: false + externals: { + react: "React", + React: "React", + redux: "Redux", + "react-dom": "ReactDOM", + "react-redux": "ReactRedux", + "react-router": "ReactRouter", + "styled-components": "styled" + // "react-router-dom": "ReactRouterDOM" }, module: { rules: [