diff --git a/package.json b/package.json index b603353..126b9ba 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", + "@theme-ui/color": "^0.4.0-rc.5", "@theme-ui/components": "^0.3.1", "@types/hammerjs": "^2.0.36", "@types/peerjs": "^1.1.0", @@ -53,7 +54,7 @@ "peer": "^0.5.3", "peerjs": "^1.3.1", "pnp-webpack-plugin": "1.6.4", - "polished": "^3.6.5", + "polished": "^3.6.7", "postcss-flexbugs-fixes": "4.1.0", "postcss-loader": "3.0.0", "postcss-normalize": "8.0.1", @@ -67,6 +68,7 @@ "react-dom": "^16.13.1", "react-hammerjs": "^1.0.1", "react-modal": "^3.11.2", + "react-move": "^6.4.0", "react-popper": "^2.2.3", "resolve": "1.15.0", "resolve-url-loader": "3.1.1", @@ -108,6 +110,7 @@ }, "devDependencies": { "@testing-library/dom": "^7.23.0", + "@theme-ui/css": "^0.4.0-rc.5", "@types/dat.gui": "^0.7.5", "@types/jest": "^26.0.10", "@types/node": "^12.0.0", @@ -116,7 +119,7 @@ "@types/react-dom": "^16.9.0", "@types/react-hammerjs": "^1.0.1", "@types/react-modal": "^3.10.6", - "@types/theme-ui": "^0.3.6", + "@types/theme-ui": "^0.3.7", "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "^3.10.1", "@typescript-eslint/parser": "^3.10.1", diff --git a/public/images/sun_segment.png b/public/images/sun_segment.png new file mode 100644 index 0000000..b9986bb Binary files /dev/null and b/public/images/sun_segment.png differ diff --git a/public/images/sun_segment.svg b/public/images/sun_segment.svg new file mode 100644 index 0000000..0a21963 --- /dev/null +++ b/public/images/sun_segment.svg @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/src/3d/constants.ts b/src/3d/constants.ts index 89b01ca..092fdc9 100644 --- a/src/3d/constants.ts +++ b/src/3d/constants.ts @@ -3,6 +3,7 @@ import { Vector3 } from 'three' export const TAU = Math.PI * 2 export const MODELS_LOCATION = process.env.PUBLIC_URL + '/models' +export const IMAGES_LOCATION = process.env.PUBLIC_URL + '/images' export const MODELS = { BLUE_TOP: 'blueTop', @@ -63,16 +64,14 @@ export const COLOR_VALUES: { [k in Color]: number } = { } export const SHADE_Y: { [k in GrowthStage]: number } = [-18, -10.7, -5.63, 0] - export const SUN_ANGLE = 52.725 - export const TREE_TOP_Y = 10 - export const TILE_SIZE = 5 - export const INITIAL_SUN_ORIENTATION = 1.50 * Math.PI - export const GROUND_SHADE_HIDDEN_ROTATION = -0.8 +export const SUN_SEGMENT_SIZE = 56 +export const SUN_SEGMENT_POSITION_Y = 2.2 +export const SUN_SEGMENT_POSITION_Z = SUN_SEGMENT_SIZE / 2 export interface TreeGrowthProp { tree: { scale: Vector3 } diff --git a/src/3d/extraObjects.ts b/src/3d/extraObjects.ts index f195d88..945b09d 100644 --- a/src/3d/extraObjects.ts +++ b/src/3d/extraObjects.ts @@ -1,4 +1,5 @@ -import { CylinderBufferGeometry, Mesh, MeshBasicMaterial, Object3D } from 'three' +import { CylinderBufferGeometry, Mesh, MeshBasicMaterial, Object3D, PlaneBufferGeometry, TextureLoader } from 'three' +import { IMAGES_LOCATION, SUN_SEGMENT_SIZE } from './constants' const cylinderGeometry = new CylinderBufferGeometry(4, 4, 1, 12) const cylinderMaterial = new MeshBasicMaterial() @@ -12,3 +13,11 @@ CYLINDER_OBJ.add(cylinderMesh) export const basicGray = new MeshBasicMaterial({ color: 0xcccccc }) + +const sunSegmentTexture = new TextureLoader().load( + IMAGES_LOCATION + '/sun_segment.png', undefined, undefined, console.error +) +const sunSegmentMaterial = new MeshBasicMaterial({ transparent: true, map: sunSegmentTexture }) +const sunSegmentGeometry = new PlaneBufferGeometry(SUN_SEGMENT_SIZE, SUN_SEGMENT_SIZE) +export const sunSegmentMesh = new Mesh(sunSegmentGeometry, sunSegmentMaterial) +sunSegmentMesh.rotation.x = -Math.PI / 2 diff --git a/src/Game/GameWorld.ts b/src/Game/GameWorld.ts index 374be0d..9af953e 100644 --- a/src/Game/GameWorld.ts +++ b/src/Game/GameWorld.ts @@ -26,7 +26,7 @@ import { MODELS, SKY_COLOR, SUN_ANGLE, - SUN_COLOR, + SUN_COLOR, SUN_SEGMENT_POSITION_Y, SUN_SEGMENT_POSITION_Z, TAU, TREE_GROWTH_DURATION } from '../3d/constants' @@ -38,7 +38,7 @@ import SunOrientationTagComponent from './components/SunOrientationTagComponent' import SunOrientationSystem from './systems/SunOrientationSystem' import dat from 'dat.gui' import { Axial } from '../3d/Coordinates/Axial' -import { CYLINDER_OBJ } from '../3d/extraObjects' +import { CYLINDER_OBJ, sunSegmentMesh } from '../3d/extraObjects' import Stats from 'stats.js' import GameWorldMessages from './types/GameWorldMessages' import { TileInfo } from './types/TileInfo' @@ -265,6 +265,21 @@ export default class GameWorld { .addObject3DComponent(game, this.sceneEntity) this.generateGrid() + + const sunSegmentWrapperObj = new Object3D() + sunSegmentWrapperObj.name = 'sunSegmentWrapper' + const sunSegmentObj = new Object3D() + sunSegmentObj.name = 'sugSegment' + sunSegmentObj.position.y = SUN_SEGMENT_POSITION_Y + sunSegmentObj.position.z = SUN_SEGMENT_POSITION_Z + sunSegmentObj.rotation.y = Math.PI + sunSegmentObj.add(sunSegmentMesh) + sunSegmentWrapperObj.add(sunSegmentObj) + + this.world + .createEntity('sunSegment') + .addObject3DComponent(sunSegmentWrapperObj, this.gameEntity) + .addComponent(SunOrientationTagComponent) } private generateGrid (): void { diff --git a/src/Game/getInitialState.ts b/src/Game/getInitialState.ts index befd1a7..b5245bc 100644 --- a/src/Game/getInitialState.ts +++ b/src/Game/getInitialState.ts @@ -40,6 +40,7 @@ export const getInitialState = (players: number): GameState => { dirtyTiles: [], turn: 0, rayDirection: 0, + totalRevolutions: 3, revolutionLeft: 3, board: getInitialBoard(), scoreTokens: { diff --git a/src/Game/types/GameState.ts b/src/Game/types/GameState.ts index 8a4675e..c927f01 100644 --- a/src/Game/types/GameState.ts +++ b/src/Game/types/GameState.ts @@ -6,6 +6,7 @@ export interface GameState { dirtyTiles: string[] turn: number rayDirection: number + totalRevolutions: number revolutionLeft: number gameOver?: string board: TileMap diff --git a/src/components/App.tsx b/src/components/App.tsx index c167e56..dd8e9c3 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -9,6 +9,7 @@ import { GamePlayer } from './GamePlayer' import { withAlertQueue } from './common/AlertContext' import GameRenderer from './GameRenderer' import { Box } from '@theme-ui/components' +import RevolutionCounter from './RevolutionCounter' export enum AppState { HOME, @@ -61,6 +62,9 @@ const App: FunctionComponent = () => { // } + + + diff --git a/src/components/GamePlayer.tsx b/src/components/GamePlayer.tsx index db5a5f3..8f4c821 100644 --- a/src/components/GamePlayer.tsx +++ b/src/components/GamePlayer.tsx @@ -90,7 +90,7 @@ export const GamePlayer: FunctionComponent + {hintText} {game.state !== undefined && interactionState.axial !== undefined && interactionState.popperCoord !== undefined && > = ({ state, setState }) => { +const NavBar: FunctionComponent> = () => { const [colorMode, setColorMode] = useColorMode() const toggleDarkMode = (): void => setColorMode(colorMode === 'default' ? 'dark' : 'default') return ( - + // eslint-disable-next-line + // @ts-ignore + // eslint-disable-next-line + {/* {state > 0 && } */} diff --git a/src/components/RevolutionCounter.tsx b/src/components/RevolutionCounter.tsx new file mode 100644 index 0000000..54a675b --- /dev/null +++ b/src/components/RevolutionCounter.tsx @@ -0,0 +1,371 @@ +import React, { FunctionComponent } from 'react' +import { Animate, NodeGroup } from 'react-move' +import { TAU } from '../3d/constants' +import { getColor } from '@theme-ui/color' +import { BoxProps, useThemeUI } from 'theme-ui' +import { Box } from '@theme-ui/components' +import { darken, lighten } from 'polished' +import easeOutQuart from '../Game/easing/1d/easeOutQuart' +import { useGame } from '../Game/GameContext' + +const getRemainingSlices = (elapsedRounds: number, circleIndex: number): number => { + if (elapsedRounds / 6 < circleIndex) { + return 6 + } else if (elapsedRounds / 6 >= circleIndex + 1) { + return 0 + } else { + return 6 - elapsedRounds % 6 + } +} + +const isPieEnlarged = (elapsedRounds: number, circleIndex: number): boolean => { + return elapsedRounds / 6 >= circleIndex +} + +const getCoordinatesOnCircle = (percentageRemaining: number): { x: number, y: number } => { + return { + x: -Math.sin(TAU * percentageRemaining), + y: -Math.cos(TAU * percentageRemaining) + } +} + +const BORDER_WIDTH = 0.08 + +const RevolutionCounter: FunctionComponent = ({ + ...boxProps +}) => { + const [game] = useGame() + const state = game.state + const preparingRounds = state?.preparingRound ?? 0 + const isPreparation = preparingRounds > 0 + const totalRounds = isPreparation ? 6 : (state?.totalRevolutions ?? 0) * 6 + const elapsedRounds = isPreparation ? 6 - preparingRounds : totalRounds - (state?.revolutionLeft ?? 0) * 6 + (6 - (state?.rayDirection ?? 0)) % 6 + + // eslint-disable-next-line + const circleIndexes = React.useMemo(() => [...Array(Math.ceil(totalRounds / 6)).keys()], [elapsedRounds, totalRounds]) + const { theme } = useThemeUI() + // eslint-disable-next-line + // @ts-ignore + const bg = getColor(theme, 'bgs.2') as string + // eslint-disable-next-line + // @ts-ignore + const accent = getColor(theme, isPreparation ? 'blue.0' : 'yellow.0') as string + const shadow = darken(0.1, bg) + const highlight = lighten(0.1, bg) + const { sx, ...otherBoxProps } = boxProps + return ( + + + + {`${totalRounds - elapsedRounds}${isPreparation ? ' preparation' : ''} turns remaining`} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x.toString()} + start={(_, i) => ({ + remaining: Math.min(0.9999, getRemainingSlices(elapsedRounds, i) / 6), + scale: isPieEnlarged(elapsedRounds, i) ? 1 : 0.3, + x: isPieEnlarged(elapsedRounds, i) ? 0 : i - Math.floor((elapsedRounds) / 6) + 0.85 + })} + update={(_, i) => ({ + remaining: [Math.min(0.9999, getRemainingSlices(elapsedRounds, i) / 6)], + scale: [isPieEnlarged(elapsedRounds, i) ? 1 : 0.3], + x: [isPieEnlarged(elapsedRounds, i) ? 0 : i - Math.floor((elapsedRounds) / 6) + 0.85], + timing: { duration: 800, ease: easeOutQuart } + })}> + {nodes => ( + + {nodes.map(({ key, state: { remaining, scale, x } }: { key: string, state: { remaining: number, scale: number, x: number } }) => { + const isLargeArc = remaining >= 0.5 ? 1 : 0 + const { x: arcX, y: arcY } = getCoordinatesOnCircle(remaining) + const maskId = `mask-${key}` + return ( + + + + + + + + + + + ) + })} + + )} + + + {({ rotation }) => { + return ( + + ) + }} + + + + + + + {totalRounds - elapsedRounds} + + + + + ) +} + +export default RevolutionCounter diff --git a/src/theme/buttons.js b/src/theme/buttons.js index e3d97b6..f6b41e0 100644 --- a/src/theme/buttons.js +++ b/src/theme/buttons.js @@ -99,6 +99,9 @@ const buttonColors = { } } +/** + * @type {Record} + */ const buttons = {} for (const styleName in buttonStyles) { diff --git a/src/theme/index.js b/src/theme/index.ts similarity index 88% rename from src/theme/index.js rename to src/theme/index.ts index 0b85ba0..3147b6d 100644 --- a/src/theme/index.js +++ b/src/theme/index.ts @@ -10,8 +10,9 @@ import text from './text' import wells from './wells' import messages from './messages' import badges from './badges' +import { Theme } from '@theme-ui/css' -export default { +const theme: Theme = { useColorSchemeMediaQuery: true, colors: { ...light, @@ -24,6 +25,8 @@ export default { danger: colors.red[0], warning: colors.yellow[0] }, + // eslint-disable-next-line + // @ts-ignore text, breakpoints: ['40em', '52em', '64em', '110em'], space: [ @@ -65,7 +68,7 @@ export default { }, radii: [0, 4, 8, 16, 32], shadows: [ - null, + 'none', '0 2px 4px rgba(0, 0, 0, 0.2)', '0 4px 8px rgba(0, 0, 0, 0.3)' ], @@ -87,5 +90,8 @@ export default { links, dropdown, wells, + // eslint-disable-next-line + // @ts-ignore styles } +export default theme diff --git a/yarn.lock b/yarn.lock index 5039e71..92d9897 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1774,6 +1774,14 @@ "@theme-ui/css" "^0.3.1" deepmerge "^4.2.2" +"@theme-ui/color@^0.4.0-rc.5": + version "0.4.0-rc.5" + resolved "https://registry.yarnpkg.com/@theme-ui/color/-/color-0.4.0-rc.5.tgz#d638f089d2030e17438810b2b760fc9704850cc4" + integrity sha512-cmKnIXKC/br+qDbzmjaHg0YDgDuGdN7slKwUc2j3aSLmvx7aKdiSFBkVUMGeFhgBy32y9SIH6jmT6g3UqhogmQ== + dependencies: + "@theme-ui/css" "^0.4.0-rc.5" + polished "^3.4.1" + "@theme-ui/components@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@theme-ui/components/-/components-0.3.1.tgz#fe023e156c1e1c076d5f2258466426e94adc2765" @@ -1800,6 +1808,13 @@ resolved "https://registry.yarnpkg.com/@theme-ui/css/-/css-0.3.1.tgz#b85c7e8fae948dc0de65aa30b853368993e25cb3" integrity sha512-QB2/fZBpo4inaLHL3OrB8NOBgNfwnj8GtHzXWHb9iQSRjmtNX8zPXBe32jLT7qQP0+y8JxPT4YChZIkm5ZyIdg== +"@theme-ui/css@^0.4.0-rc.5": + version "0.4.0-rc.5" + resolved "https://registry.yarnpkg.com/@theme-ui/css/-/css-0.4.0-rc.5.tgz#0fa254f90eecf6d8df28b1e3b4677d7fc3338d82" + integrity sha512-KJly9bIbfcMFwHiDB6WZ5bTMgjJSQssBFkyqETA1GpfK/M7AwpupPeyLzc65K8tYatMFXlUQmYo9JNPtLYawwg== + dependencies: + csstype "^2.5.7" + "@theme-ui/mdx@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@theme-ui/mdx/-/mdx-0.3.0.tgz#8bb1342204acfaa69914d6b6567c5c49d9a8c1e6" @@ -2120,7 +2135,7 @@ "@types/testing-library__dom" "*" pretty-format "^25.1.0" -"@types/theme-ui@*", "@types/theme-ui@^0.3.6": +"@types/theme-ui@*": version "0.3.6" resolved "https://registry.yarnpkg.com/@types/theme-ui/-/theme-ui-0.3.6.tgz#e7d25ef4fdaf6d3e69ef483858210df99c35c1fa" integrity sha512-VAw3NA1Ye0dMfWM4/nfDPGTL5dFuPC1FHpDnLleEh6jRmqp2OliaJIeZnffmDVnGmb9iIvCwdaLDYAmltrnypA== @@ -2132,6 +2147,18 @@ "@types/theme-ui__components" "*" csstype "^3.0.2" +"@types/theme-ui@^0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@types/theme-ui/-/theme-ui-0.3.7.tgz#67346df27d02045c7f06f9684c88e3a5622a456d" + integrity sha512-4hzDlDhlFYmOdXBLZTbO4N2hWfuGo1N77AcIMaSyDGEyFbdZSpelMLTkEtNzYT8yQWIl3x0WITiBzjqkfc6dUg== + dependencies: + "@emotion/serialize" "^0.11.15" + "@types/react" "*" + "@types/styled-system" "*" + "@types/styled-system__css" "*" + "@types/theme-ui__components" "*" + csstype "^3.0.2" + "@types/theme-ui__components@*": version "0.2.5" resolved "https://registry.yarnpkg.com/@types/theme-ui__components/-/theme-ui__components-0.2.5.tgz#65ef4e160e2e0cf7c52ae220f6579a707d33667d" @@ -4143,6 +4170,11 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= +d3-timer@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" + integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -7095,6 +7127,13 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.4.1: array-includes "^3.1.1" object.assign "^4.1.0" +kapellmeister@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/kapellmeister/-/kapellmeister-3.0.1.tgz#419b715cd221acda3db79892caedf63e1c9f7d25" + integrity sha512-S7+gYcziMREv8RxG46138mb1O4Xf9II/bCxEJPYkhlZ7PgGWTlicgsyNad/DGc5oEAlWGLXE5ExLbTDVvJmgDA== + dependencies: + d3-timer "^1.0.9" + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -8399,7 +8438,7 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" -polished@^3.6.5: +polished@^3.4.1, polished@^3.6.7: version "3.6.7" resolved "https://registry.yarnpkg.com/polished/-/polished-3.6.7.tgz#44cbd0047f3187d83db0c479ef0c7d5583af5fb6" integrity sha512-b4OViUOihwV0icb9PHmWbR+vPqaSzSAEbgLskvb7ANPATVXGiYv/TQFHQo65S53WU9i5EQ1I03YDOJW7K0bmYg== @@ -9418,6 +9457,15 @@ react-modal@^3.11.2: react-lifecycles-compat "^3.0.0" warning "^4.0.3" +react-move@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/react-move/-/react-move-6.4.0.tgz#9163a87c62fff94bf6e6980685c713739192650e" + integrity sha512-TNyDQESDZ0xsejnxFTQ9CKarJQN6NbgpImrvIEzOVe7+jt8y7uTjJwWxqFTfmvwskIs+RmUbCWdN7PAbGyhrdA== + dependencies: + "@babel/runtime" "^7.10.3" + kapellmeister "^3.0.1" + prop-types "^15.7.2" + react-popper@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.3.tgz#33d425fa6975d4bd54d9acd64897a89d904b9d97"