본문 바로가기
Projects/Socket Omok

[Socket Omok] 6. 착수 위치 미리보기 및 선택하기

by DevJaewoo 2022. 6. 13.
반응형

착수 위치 미리보기

미리보기가 없어서 '여기쯤 클릭하면 되겠지' 하고 착수했는데 엉뚱한곳에 착수되면 엄청 억울할것이다.

적어도 '내가 지금 클릭하면 여기에 놔지겠구나' 정도는 알려줄 수 있어야 한다.

 

우선 바둑판의 크기와 같은 div를 바둑판의 위에 이벤트 감지용으로 만들었다.

원래는 바둑판 자체에서 이벤트를 수신했는데, 바둑알을 놓으면 바둑알이 이벤트 수신을 방해해서 새 컴포넌트를 만들었다.

이벤트 수신용 컴포넌트 구성
대충 이런 느낌이다.

 

우선 바둑알 컴포넌트의 prop을 수정했다.

기존에는 white인지 아닌지만 true : false로 받았는데, 배열로 들어갈 속성들을 받아서 class를 추가해주는 방식으로 변경했다.

 

const stone = ({ type, x, y }) => {
  let material = "";
  type.forEach((m) => {
    switch (m) {
      case "black":
        material += " omokboard__stone--black";
        break;
      case "white":
        material += " omokboard__stone--white";
        break;
      case "hint":
        material += " omokboard__stone--hint";
        break;
      case "prev":
        material += " omokboard__stone--prev";
        break;
    }
  });

  return (
    <div
      className={`omokboard__stone ${material}`}
      key={`${x}${y}`}
      style={{
        left: `${x * BOARD_SPACE + BOARD_OFFSET}%`,
        top: `${y * BOARD_SPACE + BOARD_OFFSET}%`,
      }}
    ></div>
  );
};

 

클래스에 따른 CSS도 추가해줬다.

.omokboard__stone {
  position: absolute;
  width: 4.5%;
  height: 4.5%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  box-shadow: 0px 0px 5px 0px gray;
}

.omokboard__stone--black {
  background-color: black;
}

.omokboard__stone--white {
  background-color: white;
}

.omokboard__stone--hint {
  opacity: 0.6;
}

.omokboard__stone--prev {
  width: 2%;
  height: 2%;
  background-color: red;
  z-index: 1;
}

 

이제 아래와 같이 클래스 리스트만 변경하여 바둑알의 모양을 변경할 수 있다.

왼쪽부터 black, white, black + prev, white + hint이다.

바둑알 종류

 

그 다음 이벤트를 감지하는 컴포넌트를 만들어줬다.

부모로부터 Listener 4개를 받는데, 각각의 역할은 다음과 같다.

 

  • onBoardEnter: 마우스가 오목판 안으로 들어오거나 터치가 시작된 경우. 이때부터 착수 힌트를 보여준다.
  • onBoardMove: 착수 위치가 변경되었을 경우. 착수 위치를 업데이트 해준다.
  • onBoardLeave: 마우스가 오목판 밖으로 나갔거나 터치가 끝난 경우. 착수 힌트를 제거한다.
  • onBoardSelect: 마우스를 클릭하거나 터치가 끝난 경우. 서버에 착수 메시지를 보낸다.

모바일 사용자를 위해 터치 이벤트도 추가했으며, 손가락으로 착수 위치를 가릴것을 우려하여 손가락보다 2칸 위에 착수되도록 설정했다.

 

모바일 감지 코드는 아래와 같다.

function Mobile() {
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
    navigator.userAgent
  );
}
const isMobile = Mobile();

 

const CoordSelectArea = ({
  onBoardEnter,
  onBoardMove,
  onBoardLeave,
  onBoardSelect,
}) => {
  function getCoord(event) {
    let coordX = 0;
    let coordY = 0;

    if (!isMobile) {
      const percentX =
        (event.nativeEvent.offsetX * 100.0) / event.target.clientWidth;
      const percentY =
        (event.nativeEvent.offsetY * 100.0) / event.target.clientHeight;

      coordX = parseInt((percentX - BOARD_OFFSET) / BOARD_SPACE + 0.5);
      coordY = parseInt((percentY - BOARD_OFFSET) / BOARD_SPACE + 0.5);
    } else {
      const bcr = event.target.getBoundingClientRect();
      const x = event.targetTouches[0].clientX - bcr.x;
      const y = event.targetTouches[0].clientY - bcr.y;

      const percentX = (x * 100.0) / event.target.clientWidth;
      const percentY = (y * 100.0) / event.target.clientHeight;
      coordX = parseInt((percentX - BOARD_OFFSET) / BOARD_SPACE + 0.5);
      coordY = parseInt((percentY - BOARD_OFFSET) / BOARD_SPACE - 1.5); //모바일 터치 배려
    }

    if (coordX < 0) coordX = 0;
    if (coordY < 0) coordY = 0;

    if (coordX > 18) coordX = 18;
    if (coordY > 18) coordY = 18;

    return {
      x: coordX,
      y: coordY,
    };
  }

  const onMouseEnter = () => {
    if (isMobile) return;
    onBoardEnter();
  };

  const onMouseMove = (event) => {
    if (isMobile) return;
    onBoardMove(getCoord(event));
  };

  const onMouseLeave = () => {
    if (isMobile) return;
    onBoardLeave();
  };

  const onMouseClick = () => {
    if (isMobile) return;
    onBoardSelect();
  };

  const onTouchStart = (event) => {
    if (!isMobile) return;
    onBoardEnter();
    onBoardMove(getCoord(event));
  };

  const onTouchMove = (event) => {
    if (!isMobile) return;
    onBoardMove(getCoord(event));
  };

  const onTouchEnd = (event) => {
    if (!isMobile) return;
    onBoardLeave();
    onBoardSelect();
  };

  return (
    <div
      className="omokboard__coord"
      onMouseEnter={onMouseEnter}
      onMouseMove={onMouseMove}
      onMouseLeave={onMouseLeave}
      onClick={onMouseClick}
      onTouchStart={onTouchStart}
      onTouchMove={onTouchMove}
      onTouchEnd={onTouchEnd}
    ></div>
  );
};

 

그리도 컴포넌트의 부모인 OmokBoard에 위의 컴포넌트를 추가하고, Listener들을 등록했다.

착수 시 서버에 "player_selected" 메시지를 보내도록 설정했다.

const OmokBoard = ({ takes }) => {
  const [inBoard, setInBoard] = React.useState(false);
  const [coord, setCoord] = React.useState({});

  const handleBoardEnter = () => {
    setInBoard(true);
  };

  const handleBoardLeave = () => {
    setInBoard(false);
  };

  const handleBoardMove = (coord) => {
    //이미 돌이 존재하면 건너뜀
    if (takes.find((c) => c.x === coord.x && c.y === coord.y) === undefined) {
      setCoord(coord);
    }
  };

  const handleBoardSelect = () => {
    setMyTurn(false);
    console.log(`Select [${coord.x},${coord.y}]`);
    socket.emit("player_selected", coord);
  };

  return (
    <div className="omokboard">
      <CoordSelectArea
        onBoardEnter={handleBoardEnter}
        onBoardMove={handleBoardMove}
        onBoardLeave={handleBoardLeave}
        onBoardSelect={handleBoardSelect}
      />
      {takes.map((takes, index) => (
        <MemoriedStone
          type={[index % 2 === 0 ? "black" : "white"]}
          x={takes.x}
          y={takes.y}
        />
      ))}
      {inBoard ? (
        <MemoriedStone
          type={[takes.length % 2 == 0 ? "black" : "white", "hint"]}
          x={coord.x}
          y={coord.y}
        />
      ) : null}
    </div>
  );
};

 

적용 결과
아주 잘 된다.

 

반응형