Slide Puzzle をつくってみました


Reactをちょっと使ってみたくて、スライドパズルをつくってみました。

![Puzzle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165394/2a7e0003-8d87-0574-1b7e-9435d5135e70.png)



# 少しこだわったところ


3個の駒をまとめて動かせる
Flutterのサンプルのパズル（旧版）のように、自動で動く

# 残念なところ


reactの使い方、javascriptの使い方、そしてデザインが残念です。

# codesandbox

https://giq1pt.csb.app/

# コード


```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Slider from '@mui/material/Slider';
import Typography from '@mui/material/Typography';
import { createTheme, ThemeProvider } from '@mui/material/styles';

const Tile = ({position, num, onClick}) => {
  const tileSize = 8 * 4 + 20 + 1 + 4;
  if (num === 0 ) { return null; }
  return (
    <Box
      sx={{
        userSelect:'none',
        border: '1px solid',
        p: 2,
        position: 'absolute',
        width: 20,
        height: 20,
        textAlign: 'center',
        top: Math.trunc(position/4) * tileSize + 10,
        left: position % 4 * tileSize + 10,
        transition:  (theme) => theme.transitions.create(['top', 'left'], 
        {
          duration:  300,
          easing: 'cubic-bezier(.1,-0.35,.21,1.62)'
        }),
      }}
      onClick = {() => onClick(position)}
    >
      {num}
    </Box>)
};

export default function Game() {
  const [tilePositions, setTilePositions] = React.useState( [...Array(16)].map((_, i) => i));
  const [autoMode, setAutoMode] = React.useState(false);
  const [autoUpDown, setAutoUpDown] = React.useState(false);
  const [updateInterval, setUpdateInterval] = React.useState(250);

  const sliderChange = (event, newValue) => {
    setUpdateInterval(newValue);
  };

  React.useEffect(() => {
    const moveAuto = () =>{
      let x = tilePositions[0] % 4;
      let y = Math.trunc(tilePositions[0] / 4);
      let [xx, yy] = [[0,1,2,3], [0,1,2,3]];
      xx.splice(x, 1);
      yy.splice(y, 1);
      const [moveToX, moveToY] = [xx[Math.floor(Math.random()*3)], yy[Math.floor(Math.random()*3)]] 
      move((autoUpDown ? x + 4 * moveToY : moveToX + 4 * y), tilePositions);
      setAutoUpDown(v => !v)
    }
    const timer = setTimeout(() => {
      if (autoMode){
        moveAuto();
      }
    }, updateInterval);
    return () => {
      clearTimeout(timer);
    };
  }, [autoMode, autoUpDown, tilePositions,updateInterval]);

  const shuffleTile = ([...positions]) =>{
    for (let i = 0; i < positions.length; i++) {
      const j = Math.floor(Math.random() * (positions.length -i)) + i;
      [positions[i], positions[j]] = [positions[j], positions[i]];
    }
    setTilePositions(positions);
  }

  const move = (posClicked, [...positions]) =>
  {
    const posEmpty = positions[0];
    const isMoveLeftRight = Math.trunc(posEmpty/4) === Math.trunc(posClicked/4);
    if (!isMoveLeftRight && !(posEmpty % 4 ===  posClicked % 4)) // && isMoveUpDown
    {
      return;
    }
    const deltaIndex = isMoveLeftRight ? 1: 4;
    const deltaPosition = Math.sign(posEmpty - posClicked) * deltaIndex;
    const pos = [...positions];
    const [minPos, maxPos]= [Math.min(posEmpty, posClicked), Math.max(posEmpty, posClicked)];
    for (let p = minPos; p < maxPos + 1; p += deltaIndex ){
      let index = positions.findIndex(v => v === p);
      p === posEmpty ? pos[0] = posClicked : pos[index] = p + deltaPosition;
    }
    setTilePositions(pos)
  }

  return (
    <ThemeProvider theme={createTheme({})}>
      <Box sx={{position: 'relative', ml: 'autoMode', mr: 'autoMode', width:220, height:300}}>
        <Typography>Slide Puzzle</Typography>
        <Button onClick={() => shuffleTile(tilePositions)}>Shuffle</Button>
        <Button onClick={() => setAutoMode(!autoMode)}>{autoMode ? 'manual' : 'automatic'}</Button>
        <Typography id="update-slider" gutterBottom>Automatic Update Interval</Typography>
        <Slider value={updateInterval} onChange={sliderChange} min={0} step={10} max={500} aria-labelledby="update-slider"/>
        <Box sx={{position: 'relative', ml: 'autoMode', mr: 'autoMode', width:250, height:250}}>
          {tilePositions.map((p, n) => { 
            return (<Tile key = {n} num = {n} position = {p} onClick = {(p) => move(p, tilePositions)}></Tile>)
          })}
        </Box>
      </Box>
    </ThemeProvider> 
  );
}

ReactDOM.render(
  <React.StrictMode>
    <Game />
  </React.StrictMode>,
  document.getElementById('root')
);



```