Search code examples
javascriptreactjstransformcss-transforms

Rotating Slot Machine is unpredictable after first rotate


I have this component that should simulate a slot machine to rotate to the numbered face to the number that is passed into props.slots.

It works the first time but doesn't seem to work after the initial spin. I can never predict even by hand which number face it will land on. What's wrong with my math?

I have 5 faces so I divided 360/5 = 72 deg in between each face.

Then have (current - next) * 72deg but this doesn't give me the correct face!

const {
  useEffect,
  useRef,
  useState
} = React;

function rnd(min, max) {
  return Math.floor(Math.random() * (max - min)) + min
}

const SlotMachine = (props) => {
  const ringElements = useRef();
  const [state, setState] = useState({
    manual: true,
    rings: [],
    slots: []
  });
  const slotMap = {
    0: 0,
    1: 72,
    2: 144,
    3: 216,
    4: 288
  };

  useEffect(() => {
    const newState = { ...state,
      slots: props.slots,
      manual: false
    };
    setState(newState);
  }, [props.slots]);

  useEffect(() => {
    if (state.manual === false) {
      rotate();
    }
    setState({ ...state,
      manual: true
    });
  }, [state.manual]);

  const rotate = () => {
    let reRing = [];
    state.rings.forEach((el, i) => {
      console.log(`--- RING ${i} ----`);
      console.log(`----ROTATE FROM ${state.rings[i].curr} ROTATE TO ${state.slots[i]} Face ---`);
      console.log(state);
      let obj = el;
      let element = obj.ringElement;
      console.log(element);
      let turns = (obj.curr - state.slots[i]);
      let move = (obj.curr === null) ? slotMap[state.slots[i]] : (turns * 72);
      console.log(`TURN BY ${turns}`);
      //move += (360 * rnd(3,6));
      console.log(`rotateX(${move}deg) for ring${i}`);
      element.style.transform = `rotateX(${move}deg)`;
      console.log(`storing ${state.slots[i]} as the obj.curr replacing ${obj.curr}`);
      obj.curr = state.slots[i];
      reRing = [...reRing, obj];
    });
    console.log('update rings:');
    setState({ ...state,
      rings: reRing
    });
  }

  useEffect(() => {
    let mountRings = [];
    Array.from(ringElements.current.children).forEach((el, i) => {
      let obj = {};
      obj.ringElement = el;
      obj.curr = null;
      mountRings = [...mountRings, obj];
    });
    console.log(mountRings);
    setState({ ...state,
      rings: mountRings
    });
  }, []);

  return(
    <div>
      <div className="slots">
        <div className="rings" ref={ringElements}>
          <div className="ring">
            <div className="slot" id="0">0</div>
            <div className="slot" id="1">1</div>
            <div className="slot" id="2">2</div>
            <div className="slot" id="3">3</div>
            <div className="slot" id="4">4</div>
          </div>
          <div className="ring">
          <div className="slot" id="0">0</div>
          <div className="slot" id="1">1</div>
          <div className="slot" id="2">2</div>
          <div className="slot" id="3">3</div>
          <div className="slot" id="4">4</div>
          </div>
          <div className="ring">
          <div className="slot" id="0">0</div>
          <div className="slot" id="1">1</div>
          <div className="slot" id="2">2</div>
          <div className="slot" id="3">3</div>
          <div className="slot" id="4">4</div>
          </div>
        </div>
      </div>
      <button className="spin-button" onClick={() => { state.manual ? rotate() : setState({...state, manual:true })  }}>SPIN</button>
    </div>
  );
}

ReactDOM.render(
  <SlotMachine slots={3} />,
  document.getElementById('app')
);
:root {
  --pushZ: 27vmin;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: "Rozha One", serif;
  font-size: 4vmin;
  color: #d99748;
  user-select: none;
}

body,
.slots,
.rings,
.ring,
.slot,
.spin {
  display: flex;
  justify-content: center;
  align-items: center;
  transform-style: preserve-3d;
}

body {
  flex-direction: column;
}

body {
  justify-content: space-around;
  width: 100%;
  height: 100vh;
  background: #1F1F1F;
}

.slots {
  position: relative;
  width: 120vmin;
  height: 50vmin;
}

.slots>* {
  height: 100%;
}

.rings {
  width: 50%;
  perspective: 250vmin;
}

.ring {
  position: relative;
  width: calc(100% / 3);
  height: 100%;
  transition: 3s ease-in-out;
}

.slot {
  position: absolute;
  width: 100%;
  height: 45%;
  font-size: 15vmin;
  background: rgba(31, 31, 31, 0.935);
}


/* rotate goes from away */

.slot:nth-child(1) {
  color: red;
  transform: rotateX(360deg) translateZ(var(--pushZ));
}

.slot:nth-child(2) {
  color: green;
  transform: rotateX(288deg) translateZ(var(--pushZ));
}

.slot:nth-child(3) {
  color: blue;
  transform: rotateX(216deg) translateZ(var(--pushZ));
}

.slot:nth-child(4) {
  color: purple;
  transform: rotateX(144deg) translateZ(var(--pushZ));
}

.slot:nth-child(5) {
  color: orange;
  transform: rotateX(72deg) translateZ(var(--pushZ));
}

.spin-button {
  width: 14vmin;
  height: 14vmin;
  transform: rotateX(45deg);
  border: 0.3333333333vmin solid;
  border-radius: 50%;
  font-size: 4.5454545455vmin;
  color: #b57e3c;
  background: transparent;
  outline: none;
  cursor: pointer;
  transition: 0.3s;
}

.spin-button:hover {
  color: #d99748;
}

.spin-button:active {
  color: #ffca60;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

<div id="app"></div>


Solution

  • Solved my issue! Rotation here always starts from the "origin". There is no need to reorient from the last rotation.

        element.style.transform = `rotateX(${move}deg)`;