From 9d723e447013547729244c745bc09035c18970a7 Mon Sep 17 00:00:00 2001 From: Bart Riemens Date: Wed, 28 Aug 2019 10:03:28 +0200 Subject: [PATCH] Improve control panel, add zoom and fps controls --- public/index.html | 12 ++++++ src/components/Apple.js | 20 ++++++---- src/components/Button.js | 27 +++++++++++++ src/components/ControlPanel.js | 73 +++++++++++++++++++++++++++++----- src/components/Scoreboard.js | 8 ++-- src/components/SnakePart.js | 10 ++--- src/components/Stage.js | 25 ++++++++---- src/containers/Snake.js | 33 +++++++++++---- src/redux/snake.js | 60 ++++++++++++++++++++++++++-- src/theming/theme.js | 2 +- 10 files changed, 225 insertions(+), 45 deletions(-) diff --git a/public/index.html b/public/index.html index 85ddfc3..9776439 100644 --- a/public/index.html +++ b/public/index.html @@ -4,6 +4,18 @@ Redux Demo + +
diff --git a/src/components/Apple.js b/src/components/Apple.js index 104b15a..049b331 100644 --- a/src/components/Apple.js +++ b/src/components/Apple.js @@ -1,15 +1,21 @@ import styled from "styled-components"; -export const Apple = styled.div.attrs({ role: "img", "aria-label": "apple" })` - width: ${({ theme }) => theme.snake.cell.size}; - height: ${({ theme }) => theme.snake.cell.size}; +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" + } +}))` display: inline-block; - font-size: 1.3rem; vertical-align: top; - line-height: 1.8rem; - padding-left: 0.0625rem; + &:after { - content: "${props => (!props.died ? "🍎" : "🐛")}"; + // content: "${props => (!props.died ? "🍎" : "🐛")}"; + content: "🍎"; display: inline-block; } `; diff --git a/src/components/Button.js b/src/components/Button.js index d6a98ea..9fc2c46 100644 --- a/src/components/Button.js +++ b/src/components/Button.js @@ -54,6 +54,16 @@ export const Button = (props, refs) => { return StyledNavLink.render(updatedProps); } + if (props.icon) { + return ( + + ); + } + return StyledButton.render(updatedProps); }; @@ -61,4 +71,21 @@ export const ToggleButton = styled(Button)` background-color: ${props => (props.toggle ? "silver" : null)}; `; +export const IconButton = styled(Button)` + margin: 0 0.2rem; + display: inline-block; + cursor: pointer; + + &:before { + text-shadow: 0 0 1px black, 1px 1px 3px #d0bfa3; + } +`; + +export const ToggleIconButton = styled(IconButton)` + color: ${props => (props.toggle ? "#e6b4c4" : null)}; + &:before { + text-shadow: 0 0 1px gray, 0 0 1px #d0bfa3; + } +`; + export default Button; diff --git a/src/components/ControlPanel.js b/src/components/ControlPanel.js index 180201f..2ee6a39 100644 --- a/src/components/ControlPanel.js +++ b/src/components/ControlPanel.js @@ -1,21 +1,76 @@ import React from "react"; import styled from "styled-components"; -import Button, { ToggleButton } from "../components/Button.js"; +import Button, { IconButton, ToggleIconButton } from "../components/Button.js"; + +const buttonSize = 2.5; const Layout = styled.div` display: flex; flex-direction: column; `; -export const ControlPanel = ({ updateFrameSnake, paused, pauseSnake, stopSnake, startSnake, fps }) => ( +const ButtonRow = styled.div` + display: flex; + place-content: center; + align-content: space-around; +`; + +const HorizontalStack = styled.div` + display: flex; + width: 100%; + // place-content: center; + align-content: center; + place-content: space-between; +`; + +const VerticalStack = styled.div` + display: flex; + flex-direction: column; + place-content: center; + align-content: space-around; +`; + +export const ControlPanel = ({ + updateFrameSnake, + paused, + started, + pauseSnake, + stopSnake, + startSnake, + fps, + zoom, + setFps, + zoomIn, + zoomOut +}) => ( -

Control Panel

- - {paused ? "Resume" : "Pause"} - - - - FPS: {fps} + { + //

Control Panel

+ // + // {paused ? "Resume" : "Pause"} + // + } + + + Zoom: {zoom} +
+ + +
+
+ + FPS: {fps} +
+ setFps(fps - 1)} /> + setFps(fps + 1)} /> +
+
+ + + + + +
); diff --git a/src/components/Scoreboard.js b/src/components/Scoreboard.js index 96bcf0c..7487561 100644 --- a/src/components/Scoreboard.js +++ b/src/components/Scoreboard.js @@ -76,7 +76,7 @@ const CardCSSTransition = styled(CSSTransition)` &.card-appear-active { ${Card} { - transition: transform ease-out 250ms, opacity ease-out 100ms; + transition: transform ease-in 250ms, opacity ease-in 100ms; transform: rotateX(0deg); opacity: 1; } @@ -84,7 +84,7 @@ const CardCSSTransition = styled(CSSTransition)` &.card-enter-active { ${Card} { - transition: transform ease-out 250ms 250ms, opacity ease-out 100ms; + transition: transform ease-in 250ms 250ms, opacity ease-in 100ms; transform: rotateX(0deg); opacity: 1; } @@ -94,13 +94,13 @@ const CardCSSTransition = styled(CSSTransition)` &.card-exit { ${Card} { transform: rotateX(0deg); - transition: transform ease-out 250ms, opacity ease-out 100ms 150ms; + transition: transform ease-in 250ms, opacity ease-in 100ms 150ms; opacity: 1; } &.card-exit-active { ${Card} { - transition: transform ease-out 250ms, opacity ease-out 100ms 150ms; + transition: transform ease-in 250ms, opacity ease-in 100ms 150ms; transform: rotateX(90deg); opacity: 0; } diff --git a/src/components/SnakePart.js b/src/components/SnakePart.js index a8763db..17d7154 100644 --- a/src/components/SnakePart.js +++ b/src/components/SnakePart.js @@ -1,13 +1,13 @@ import styled, { css } from "styled-components"; export const SnakePart = styled.div` - width: ${({ theme }) => theme.snake.cell.size}; - height: ${({ theme }) => theme.snake.cell.size}; + width: ${({ theme, zoom }) => theme.snake.cell.size * (zoom || 1)}rem; + height: ${({ theme, zoom }) => theme.snake.cell.size * (zoom || 1)}rem; display: inline-block; - font-size: 1.3rem; + font-size: ${props => (props.zoom || 1) * 1.3}rem; vertical-align: top; - line-height: 1.8rem; - padding-left: 0.0625rem; + 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/components/Stage.js b/src/components/Stage.js index 4330ab9..85b80c0 100644 --- a/src/components/Stage.js +++ b/src/components/Stage.js @@ -1,26 +1,35 @@ import React from "react"; import styled from "styled-components"; -const Row = styled.div` +const Row = styled.div.attrs(({ theme, zoom }) => ({ + style: { + height: theme.snake.cell.size * (zoom || 1) + "rem" + } +}))` white-space: nowrap; `; -const Cell = styled.div` - width: ${({ theme }) => theme.snake.cell.size}; - height: ${({ theme }) => theme.snake.cell.size}; - border: ${({ theme }) => theme.snake.cell.border}; +const Cell = 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 + } +}))` display: inline-block; vertical-align: top; text-align: center; box-shadow: 1px 1px 4px gray inset, -1px -1px 4px #fff inset; `; -export const Stage = ({ data, children }) => ( +export const Stage = ({ data, children, zoom = 1 }) => (
{data.map((r, y) => ( - + {r.map((c, x) => ( - {children(c)} + + {children(c, zoom)} + ))} ))} diff --git a/src/containers/Snake.js b/src/containers/Snake.js index 3c3c1f6..0df77a1 100644 --- a/src/containers/Snake.js +++ b/src/containers/Snake.js @@ -3,7 +3,7 @@ import { connect } from "react-redux"; import styled from "styled-components"; /* Redux Modules */ -import { module as gameModule, keyPress } from "../redux/game.js"; +import { module as gameModule, keyPress, setFps } from "../redux/game.js"; import { module as snakeModule, startSnake, @@ -11,7 +11,9 @@ import { pauseSnake, updateFrameSnake, keyPressedSnake, - keys + keys, + zoomIn, + zoomOut } from "../redux/snake.js"; /* Components */ @@ -24,12 +26,15 @@ import Apple from "../components/Apple.js"; const Layout = styled.div` display: flex; width: 100%; - place-content: space-between; + place-content: center; + margin-top: 2rem; `; const SidePanel = styled.div` display: flex; flex-direction: column; + margin-left: 2rem; + place-content: space-between; `; class Snake extends React.Component { @@ -61,27 +66,36 @@ class Snake extends React.Component { snake, died, fps, - score + setFps, + score, + zoom, + zoomIn, + zoomOut } = this.props; return ( - + {cell => - (cell.type === "apple" && ) || - (cell.type === "snake" && ) + (cell.type === "apple" && ) || + (cell.type === "snake" && ) } @@ -105,7 +119,10 @@ const mapActionsToProps = { pauseSnake, updateFrameSnake, keyPressedSnake, - keyPress + keyPress, + setFps, + zoomIn, + zoomOut }; export default connect( diff --git a/src/redux/snake.js b/src/redux/snake.js index 5e4bb06..a82d4a2 100644 --- a/src/redux/snake.js +++ b/src/redux/snake.js @@ -10,9 +10,11 @@ import { const MODULE_NAME = "SNAKE"; const DEFAULT_GRID_SIZE = 16; +const DEFAUL_ZOOM_STEP = 0.1; const initialState = { grid: createGrid(DEFAULT_GRID_SIZE), + zoom: 1.5, size: DEFAULT_GRID_SIZE, paused: false, vX: 1, @@ -46,7 +48,18 @@ export const keys = { snakePart: "snakePart", s: "s", - r: "r" + 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) { @@ -106,6 +119,9 @@ export const [START_SNAKE, startSnake] = module.action("START_SNAKE"); export const [RESET_SNAKE, resetSnake] = module.action("RESET_SNAKE", options => ({ ...options })); export const [STOP_SNAKE, stopSnake] = module.action("STOP_SNAKE"); export const [PAUSE_SNAKE, pauseSnake] = module.action("PAUSE_SNAKE"); +export const [ZOOM_IN, zoomIn] = module.action("ZOOM_IN", step => ({ step })); +export const [SET_ZOOM_LEVEL, setZoomLevel] = module.action("SET_ZOOM_LEVEL", level => ({ level })); +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 })); @@ -117,8 +133,18 @@ export const [UPDATE_STATE, updateState] = module.action("UPDATE_STATE", (newSta /* === Reducers ================================================================================= */ module.reducer(UPDATE_STATE, (state, { newState, fullUpdate }) => (fullUpdate ? newState : { ...state, ...newState })); + +module.reducer(ZOOM_IN, (state, { step = DEFAUL_ZOOM_STEP }) => ({ + ...state, + zoom: Math.round((state.zoom + step) * 10) / 10 +})); +module.reducer(SET_ZOOM_LEVEL, (state, { level }) => ({ ...state, zoom: level })); +module.reducer(ZOOM_OUT, (state, { step = DEFAUL_ZOOM_STEP }) => + state.zoom - step >= 0.5 ? { ...state, zoom: Math.round((state.zoom - step) * 10) / 10 } : { ...state, zoom: 1 } +); module.reducer(RESET_SNAKE, (state, options) => { const grid = createGrid(state.size); + const zoom = state.zoom; const apple = options.started ? randomPosition(state.size) : initialState.apple; const snake = options.started ? [[0, 0]] : initialState.snake; @@ -126,7 +152,7 @@ module.reducer(RESET_SNAKE, (state, options) => { 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, zoom, snake, apple, ...options }; }); module.reducer(CHANGE_DIRECTION, (state, { direction }) => { @@ -223,7 +249,7 @@ module.middleware(START_SNAKE, (dispatch, { started }) => { module.middleware(STOP_SNAKE, (dispatch, { started }) => { dispatch(unregisterCaller(UPDATE_FRAME_SNAKE)); - dispatch(unsubscribeKeyPressed(KEY_PRESSED_SNAKE)); + // dispatch(unsubscribeKeyPressed(KEY_PRESSED_SNAKE)); dispatch(stopLoop()); dispatch(resetSnake({ started: false })); }); @@ -237,6 +263,7 @@ 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)); @@ -259,4 +286,31 @@ module.middleware(KEY_PRESSED_SNAKE, (dispatch, _, { key }) => { 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)); + } }); diff --git a/src/theming/theme.js b/src/theming/theme.js index 6524f81..fcc2c00 100644 --- a/src/theming/theme.js +++ b/src/theming/theme.js @@ -24,7 +24,7 @@ const themes = { snake: { cell: { border: "0.0625rem solid papayawhip", - size: "1.5rem" + size: 1.5 }, cellColors: { " ": "",