Skip to content

Instantly share code, notes, and snippets.

@Landerson352
Created October 31, 2020 20:09
Show Gist options
  • Save Landerson352/eea4fea69840b1902b3d98918049d44b to your computer and use it in GitHub Desktop.
Save Landerson352/eea4fea69840b1902b3d98918049d44b to your computer and use it in GitHub Desktop.
Sliding tile puzzle in React
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