Created
October 31, 2020 20:09
-
-
Save Landerson352/eea4fea69840b1902b3d98918049d44b to your computer and use it in GitHub Desktop.
Sliding tile puzzle in React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { Box, Flex } from "@chakra-ui/core"; | |
import _ from "lodash"; | |
import { useKey } from "react-use"; | |
import { motion } from "framer-motion"; | |
const MotionFlex = motion.custom(Flex); | |
const TILE_SIZE = 100; | |
const ROWS = 4; | |
const COLS = 4; | |
const INITIAL_TILES_STATE = _.times(ROWS * COLS, (n) => n + 1); | |
const GAP_TILE = _.last(INITIAL_TILES_STATE); | |
const Tile = ({ top, left, opacity, ...restProps }) => ( | |
<MotionFlex | |
position="absolute" | |
borderWidth={1} | |
borderStyle="solid" | |
borderColor="gray.800" | |
width={TILE_SIZE} | |
height={TILE_SIZE} | |
borderRadius={TILE_SIZE / 15} | |
alignItems="center" | |
justifyContent="center" | |
fontSize={TILE_SIZE / 2} | |
fontWeight="bold" | |
initial={{ top, left, opacity }} | |
animate={{ top, left, opacity }} | |
transition={{ duration: 0.2 }} | |
{...restProps} | |
/> | |
); | |
const TilePuzzle = () => { | |
const [isShuffling, setIsShuffling] = React.useState(true); | |
const [tiles, setTiles] = React.useState(INITIAL_TILES_STATE); | |
const hasWon = _.isEqual(tiles, INITIAL_TILES_STATE); | |
const moveGap = (x) => setTiles((prevTiles) => { | |
const nextTiles = _.cloneDeep(prevTiles); | |
const prevGapLocation = nextTiles.indexOf(GAP_TILE); | |
const nextGapLocation = prevGapLocation + x; | |
// no sliding between row edges | |
if ( | |
Math.abs(x) === 1 && | |
Math.floor(prevGapLocation / COLS) !== | |
Math.floor(nextGapLocation / COLS) | |
) | |
return nextTiles; | |
const displacedTile = _.get(nextTiles, nextGapLocation); | |
// out of bounds | |
if (!displacedTile) return nextTiles; | |
nextTiles[nextGapLocation] = GAP_TILE; | |
nextTiles[prevGapLocation] = displacedTile; | |
return nextTiles; | |
}); | |
useKey("ArrowUp", () => moveGap(COLS), {}, [moveGap]); | |
useKey("ArrowDown", () => moveGap(-COLS), {}, [moveGap]); | |
useKey("ArrowLeft", () => moveGap(1), {}, [moveGap]); | |
useKey("ArrowRight", () => moveGap(-1), {}, [moveGap]); | |
// shuffle tiles | |
React.useEffect(() => { | |
const moves = [-COLS, COLS, -1, 1]; | |
_.times(ROWS * COLS * ROWS * COLS, () => { | |
moveGap(_.sample(moves)); | |
}); | |
setIsShuffling(false); | |
}, []); | |
if (isShuffling) return null; | |
return ( | |
<Box | |
position="relative" | |
width={TILE_SIZE * COLS} | |
height={TILE_SIZE * ROWS} | |
bg="gray.800" | |
> | |
{tiles.map((number, index) => ( | |
<Tile | |
key={number} | |
left={(index % COLS) * TILE_SIZE} | |
top={Math.floor(index / COLS) * TILE_SIZE} | |
opacity={number === GAP_TILE ? 0 : 1} | |
bg={hasWon ? "green.300" : "yellow.300"} | |
> | |
{number} | |
</Tile> | |
))} | |
</Box> | |
); | |
}; | |
export default TilePuzzle; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment