diff --git a/src/components/Apple.js b/src/components/Apple.js index 049b331..5d4e882 100644 --- a/src/components/Apple.js +++ b/src/components/Apple.js @@ -1,10 +1,14 @@ -import styled from "styled-components"; +import styled, { keyframes } from "styled-components"; + +const fade = keyframes` +from { transform: scale(.7) } +to { transform: scale(1) } +`; const Apple = styled.div.attrs(({ theme, zoom }) => ({ style: { height: theme.snake.cell.size * (zoom || 1) + "rem", width: theme.snake.cell.size * (zoom || 1) + "rem", - border: theme.snake.cell.border, fontSize: (zoom || 1) * 1.3 + "rem", lineHeight: (zoom || 1) * 1.7 + "rem", paddingLeft: (zoom || 1) * 0.0625 + "rem" @@ -14,9 +18,10 @@ const Apple = styled.div.attrs(({ theme, zoom }) => ({ vertical-align: top; &:after { - // content: "${props => (!props.died ? "🍎" : "🐛")}"; - content: "🍎"; + content: "${props => (!props.died ? "🍎" : "🐛")}"; + // content: "🍎"; display: inline-block; + animation: ${fade} 1s ease-out alternate infinite; } `; diff --git a/src/components/Button.js b/src/components/Button.js index 9fc2c46..4d19697 100644 --- a/src/components/Button.js +++ b/src/components/Button.js @@ -1,6 +1,6 @@ import React from "react"; import { NavLink } from "react-router-dom"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; const SharedStyle = () => ` border: 1px solid gray; @@ -21,6 +21,11 @@ const SharedStyle = () => ` } `; +const ButtonWrapper = styled.div` + position: relative; + display: inline-block; +`; + const StyledButton = styled.button` ${SharedStyle} -webkit-appearance: none; @@ -39,8 +44,30 @@ const StyledNavLink = styled(NavLink)` } `; +const Tooltip = styled.div` + position: absolute; + margin-top: 0.5rem; + border: 1px solid gray; + background-color: palevioletred; + color: papayawhip; + padding: 5px; + box-shadow: 1px 1px 3px #d0bfa3; + ${props => + props.hover + ? css` + transition: opacity 400ms 200ms ease-in; + ` + : css` + transition: opacity 100ms ease-out; + `} + white-space: nowrap; + opacity: ${props => (props.hover ? 1 : 0)}; +`; + export const Button = (props, refs) => { + const [hover, setHover] = React.useState(false); const updatedProps = { ...props }; + let tooltip = null; if (props.onClick) { updatedProps.onClick = e => e.preventDefault() || props.onClick(); @@ -54,13 +81,20 @@ export const Button = (props, refs) => { return StyledNavLink.render(updatedProps); } + if (props.tooltip) { + tooltip = {props.tooltip}; + } + if (props.icon) { return ( - + setHover(true)} onMouseLeave={() => setHover(false)}> + + {tooltip} + ); } diff --git a/src/components/ControlPanel.js b/src/components/ControlPanel.js index 2ee6a39..77e5a38 100644 --- a/src/components/ControlPanel.js +++ b/src/components/ControlPanel.js @@ -66,10 +66,34 @@ export const ControlPanel = ({ - - - - + + + + ); diff --git a/src/components/SnakePart.js b/src/components/SnakePart.js index 17d7154..3ca3651 100644 --- a/src/components/SnakePart.js +++ b/src/components/SnakePart.js @@ -1,13 +1,16 @@ import styled, { css } from "styled-components"; -export const SnakePart = styled.div` - width: ${({ theme, zoom }) => theme.snake.cell.size * (zoom || 1)}rem; - height: ${({ theme, zoom }) => theme.snake.cell.size * (zoom || 1)}rem; +export const SnakePart = styled.div.attrs(({ theme, zoom }) => ({ + style: { + height: theme.snake.cell.size * (zoom || 1) + "rem", + width: theme.snake.cell.size * (zoom || 1) + "rem", + fontSize: (zoom || 1) * 1.3 + "rem", + lineHeight: (zoom || 1) * 1.7 + "rem", + paddingLeft: (zoom || 1) * 0.0625 + "rem" + } +}))` display: inline-block; - font-size: ${props => (props.zoom || 1) * 1.3}rem; vertical-align: top; - line-height: ${props => (props.zoom || 1) * 1.7}rem; - padding-left: ${props => (props.zoom || 1) * 0.0625}rem; ${props => !props.died diff --git a/src/containers/Snake.js b/src/containers/Snake.js index 0df77a1..4a00475 100644 --- a/src/containers/Snake.js +++ b/src/containers/Snake.js @@ -11,7 +11,6 @@ import { pauseSnake, updateFrameSnake, keyPressedSnake, - keys, zoomIn, zoomOut } from "../redux/snake.js"; @@ -41,7 +40,6 @@ class Snake extends React.Component { constructor(props) { super(props); this.handleKeyDown = this.handleKeyDown.bind(this); - this.supportedKeys = Object.values(keys); window.me = this; } @@ -63,7 +61,6 @@ class Snake extends React.Component { grid, started, paused, - snake, died, fps, setFps, diff --git a/src/enums/directions.js b/src/enums/directions.js new file mode 100644 index 0000000..9a706c4 --- /dev/null +++ b/src/enums/directions.js @@ -0,0 +1,8 @@ +export const directions = { + UP: "UP", + DOWN: "DOWN", + LEFT: "LEFT", + RIGHT: "RIGHT" +}; + +export default directions; diff --git a/src/enums/keys.js b/src/enums/keys.js new file mode 100644 index 0000000..44cb924 --- /dev/null +++ b/src/enums/keys.js @@ -0,0 +1,32 @@ +export const keys = { + UP: "ArrowUp", + RIGHT: "ArrowRight", + DOWN: "ArrowDown", + LEFT: "ArrowLeft", + + EQUAL: "=", + PLUS: "+", + MINUS: "-", + + h: "h", + j: "j", + k: "k", + l: "l", + + p: "p", + s: "s", + r: "r", + + 1: "1", + 2: "2", + 3: "3", + 4: "4", + 5: "5", + 6: "6", + 7: "7", + 8: "8", + 9: "9", + 0: "0" +}; + +export default keys; diff --git a/src/redux/game.js b/src/redux/game.js index a732695..5b67826 100644 --- a/src/redux/game.js +++ b/src/redux/game.js @@ -1,4 +1,5 @@ import Module from "./module.js"; +import keys from "../enums/keys.js"; export const MODULE_NAME = "GAME"; @@ -9,16 +10,6 @@ export const module = new Module(MODULE_NAME, { fps: 8 }); -export const keys = { - UP: "ArrowUp", - RIGHT: "ArrowRight", - DOWN: "ArrowDown", - LEFT: "ArrowLeft", - EQUAL: "=", - PLUS: "+", - MINUS: "-" -}; - export const [START_LOOP, startLoop] = module.action("START_LOOP"); export const [STOP_LOOP, stopLoop] = module.action("STOP_LOOP"); export const [LOOP, loop] = module.action("LOOP", ({ recur, skip } = {}) => ({ recur, skip })); diff --git a/src/redux/snake.js b/src/redux/snake.js index a82d4a2..2dfaa36 100644 --- a/src/redux/snake.js +++ b/src/redux/snake.js @@ -1,12 +1,7 @@ import Module from "./module.js"; -import { - registerCaller, - unregisterCaller, - startLoop, - stopLoop, - subscribeKeyPressed, - unsubscribeKeyPressed -} from "./game.js"; +import keys from "../enums/keys.js"; +import directions from "../enums/directions.js"; +import { registerCaller, unregisterCaller, startLoop, stopLoop, subscribeKeyPressed } from "./game.js"; const MODULE_NAME = "SNAKE"; const DEFAULT_GRID_SIZE = 16; @@ -28,40 +23,6 @@ const initialState = { /* === Snake Helper Functions ================================================================== */ -export const directions = { - UP: "UP", - DOWN: "DOWN", - LEFT: "LEFT", - RIGHT: "RIGHT" -}; - -export const keys = { - UP: "ArrowUp", - RIGHT: "ArrowRight", - DOWN: "ArrowDown", - LEFT: "ArrowLeft", - - h: "h", - j: "j", - k: "k", - l: "l", - - snakePart: "snakePart", - s: "s", - r: "r", - - 1: "1", - 2: "2", - 3: "3", - 4: "4", - 5: "5", - 6: "6", - 7: "7", - 8: "8", - 9: "9", - 0: "0" -}; - function randomPosition(size) { return [Math.round(Math.random() * (size - 1)), Math.round(Math.random() * (size - 1))]; } @@ -195,7 +156,7 @@ module.reducer(UPDATE_FRAME_SNAKE, state => { } /* Check if the snake hits itself */ - if (newSnake.slice(0, -1).filter((snakePart, index) => comparePositions(snakePart, nextPosition)).length) { + if (newSnake.slice(0, -1).filter(snakePart => comparePositions(snakePart, nextPosition)).length) { started = false; died = true; } @@ -240,7 +201,7 @@ module.after(UPDATE_FRAME_SNAKE, (dispatch, { died }) => { } }); -module.middleware(START_SNAKE, (dispatch, { started }) => { +module.middleware(START_SNAKE, dispatch => { dispatch(resetSnake({ started: true, died: false })); dispatch(registerCaller(UPDATE_FRAME_SNAKE)); dispatch(subscribeKeyPressed(KEY_PRESSED_SNAKE)); @@ -263,7 +224,6 @@ module.middleware(PAUSE_SNAKE, (dispatch, { paused }) => { } }); -// eslint-disable-next-line complexity module.middleware(KEY_PRESSED_SNAKE, (dispatch, _, { key }) => { if (key === keys.UP || key === keys.k) { dispatch(changeDirection(directions.UP)); @@ -280,37 +240,16 @@ module.middleware(KEY_PRESSED_SNAKE, (dispatch, _, { key }) => { if (key === keys.s) { dispatch(stopSnake()); } - if (key === keys.snakePart) { + if (key === keys.p) { dispatch(pauseSnake()); } if (key === keys.r) { dispatch(startSnake()); } - if (key === keys[1]) { - dispatch(setZoomLevel(0.6)); - } - if (key === keys[2]) { - dispatch(setZoomLevel(0.8)); - } - if (key === keys[3]) { - dispatch(setZoomLevel(1)); - } - if (key === keys[4]) { - dispatch(setZoomLevel(1.2)); - } - if (key === keys[5]) { - dispatch(setZoomLevel(1.4)); - } - if (key === keys[6]) { - dispatch(setZoomLevel(1.6)); - } - if (key === keys[7]) { - dispatch(setZoomLevel(1.8)); - } - if (key === keys[8]) { - dispatch(setZoomLevel(2)); - } - if (key === keys[9]) { - dispatch(setZoomLevel(2.2)); + + /* Map the number keys to a zoom level */ + const keyAsInt = parseInt(key, 10); + if (!isNaN(keyAsInt) && keyAsInt > 0 && keyAsInt <= 9) { + dispatch(setZoomLevel(0.6 + (keyAsInt - 1) * 0.2)); } }); diff --git a/src/redux/ui.js b/src/redux/ui.js index 4e92a63..e576149 100644 --- a/src/redux/ui.js +++ b/src/redux/ui.js @@ -6,25 +6,25 @@ export const showSpinner = () => ({ type: SHOW_SPINNER }); export const hideSpinner = () => ({ type: HIDE_SPINNER }); export const toggleSpinner = () => ({ type: TOGGLE_SPINNER }); -export const reducers = (ui = {}, action) => { +export const reducers = (state = {}, action) => { if (action.type === SHOW_SPINNER) { return { - ...ui, + ...state, spinner: true }; } if (action.type === HIDE_SPINNER) { return { - ...ui, + ...state, spinner: false }; } if (action.type === TOGGLE_SPINNER) { return { - ...ui, - spinner: !ui.spinner + ...state, + spinner: !state.spinner }; } - return ui; + return state; };