소확행 - 틱택토 v1

Seuling·2023년 4월 28일
1

소확행

목록 보기
2/2

틱택토

첫 프로젝트는 틱택토로 선정하게되었다. 이유는 리액트 공식문서의 튜토리얼이여서 한번 봐본적이있고, 간단해보여서였다.
이후 틱택토와 비슷한 UI 및 로직인 오목을 구현해보고, 한단계 더 나아가 오델로도 구현해 볼 예정이다.

먼저, 틱택토 간단할줄알았는데.......... 안보고 구현하니 막막했다.
그래서 처음엔 안보고하려다 squares라는 배열을 만드는 것 부터 못했어서... 리액트 공식문서 (리액트 번역스터디 페이지) 를 참고해서 한번 따라 만들어보고 -> 안보고 구현해보다가 막힌부분만 다시 보고 구현해보고 -> 페어프로그래밍으로 같이 구현해보고 -> 이렇게 한 3~4번을 구현해보니 이제서야 이부분 만큼은 안보고 할 수준이 되어서 블로그 글을 작성하게 되었다.

추가구현사항인 이차배열로 만들어보는 부분은 아직도 막막하지만, 이부분도 이런식으로 세네번 반복해보면 잘할수 있지않을까.. 라는 맘으로 공부를 해야겠다!

공통 구현사항 간단하게 구현해보기 (실제 구현 흐름에 맞춰서 글 작성)

구현사항

구현사항

  • 빈 사각형 9개가 (3 * 3) 존재한다.
  • 빈 사각형을 클릭하면 “O” 나 “X”가 표시된다.
    • 한 번 클릭한 표시는 변할 수 없다.
    • 처음 클릭은 “X”가 표시된다.
  • 클릭을 하여 “O” 또는 “X” 가 표시되었다면 그 다음 표시는 무조건 다른 모양이 표시된다.
    • Next Player를 보여주며 다음 표시를 알려준다. (ex: Next Player: X)
  • 승리조건을 충족한다면 게임을 멈추고 승리자를 보여준다.
    • 승리조건은 가로, 세로, 대각선을 포함하여 3개가 연속적으로 존재한다면 승리조건에 만족한다. (ex: Next player ⇒ Winner player로 변경)
    • 승리자가 정해지면 더 이상 클릭 불가

프로젝트 시작

npm create vite@latest

디렉토리 구조

src
 ┣ utils
 ┃ ┗ judgeWinner.js
 ┣ App.css
 ┣ App.jsx
 ┣ Board.jsx
 ┣ Square.jsx
 ┗ main.jsx

각각 파일 설명

main.jsx

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

App.jsx

import "./App.css";
import Board from "./Board";

function App() {
  return (
    <>
      <Board />
    </>
  );
}

export default App;

Square.jsx

const Square = ({handleClick,value}) => { 
    
  return <div className="square" onClick={handleClick}>{value}</div>;
};

export default Square;

Board.jsx

const Board = () => {

  const handleSquareClick = (i) => {
		console.log("clicked")
  };
  return (
      <div className="board">
        <Square value={"X"} handleClick={handleSquareClick} />
        <Square value={"X"} handleClick={handleSquareClick} />
        <Square value={"X"} handleClick={handleSquareClick} />
        <Square value={"X"} handleClick={handleSquareClick} />
        <Square value={"X"} handleClick={handleSquareClick} />
        <Square value={"X"} handleClick={handleSquareClick} />
        <Square value={"X"} handleClick={handleSquareClick} />
        <Square value={"X"} handleClick={handleSquareClick} />
        <Square value={"X"} handleClick={handleSquareClick} />
      </div>
  );
};

export default Board;

구현사항 설명

먼저, UI를 그려줘야한다고 생각되어서 App.jsx 에서 <Square/> 를 생성해주었다.

이 사각형들을 3*3 형태로 나타내주기위해선 <Board/> 가 필요하였다.

그 이후 App.css 에서 css 를 눈에 보일정도로만 해주었다.

.board {
  display: grid;
  grid-template-columns: repeat(3, 100px);
}

.square {
  border: 1px solid black;
  width: 100px;
  height: 100px;
}
  • 빈 사각형 9개가 (3 * 3) 존재한다.

  • 빈 사각형을 클릭하면 “O” 나 “X”가 표시된다.
    • 먼저 X만 이라도 나오게 해보자! + 클릭했을 때 “X”가 콘솔에 찍히게 해보자!

      const Board = () => {
        const [squares, setSquares] = useState(Array(9).fill(null));
      
        const handleSquareClick = (i) => {
         setSquares("X")
        };
        return (
            <div className="board">
              <Square value={"X"} handleClick={handleSquareClick} />
            </div>
        );
      };
      
      export default Board;
      
    • 클릭시 몇번째 상자를 클릭했는지 index를 콘솔에 찍어보기

      const Board = () => {
        const [squares, setSquares] = useState(Array(9).fill(null));
      
        const handleSquareClick = (index) => {
          console.log(index);
        };
      
        return (
            <div className="board">
              <Square handleClick={() => handleSquareClick(0)} />
              <Square handleClick={() => handleSquareClick(1)} />
              <Square handleClick={() => handleSquareClick(2)} />
            </div>
        );
      };
      
      export default Board;
    • 클릭하면 X 표시하기

      const handleSquareClick = (index) => {
          setSquares("X");
        };

      처음엔 단순히 setSquares(”X”)를 넣으면 될줄 알았다. 바보같았다!

      squares를 배열로 만들어줬으니 배열에 “X”를 넣는다는것이 말이안되지

      const handleSquareClick = (index) => {
          setSquares(["X"]);
        };

      이런식으로 접근해야겠지!

      그럼 원래 squares 가 배열이니 이 배열을 그대로 복사해와서 클릭한 인덱스 부분만 바꿔서 갈아끼우면 되겠다

      const handleSquareClick = (index) => {
          const nextSquares = [...squares];
          nextSquares[index] = "X";
          setSquares(nextSquares);
        };
    • 빈 사각형을 클릭하면 “O” 나 “X”가 표시된다.
      - [x] 처음 클릭은 “X”가 표시된다.
      - [x] 클릭을 하여 “O” 또는 “X” 가 표시되었다면 그 다음 표시는 무조건 다른 모양이 표시된다.

      const [squares, setSquares] = useState(Array(9).fill(null));
        const [xOrO, setXOrO] = useState(true);
        // console.log(squares);
        const handleSquareClick = (index) => {
          const nextSquares = [...squares];
          if (xOrO) {
            nextSquares[index] = "X";
          } else {
            nextSquares[index] = "O";
          }
          setSquares(nextSquares);
          setXOrO(!xOrO);
        };
    • 한 번 클릭한 표시는 변할 수 없다.

      const handleSquareClick = (index) => {
          const nextSquares = [...squares]; 
          if (squares[index]) return; //추가
          if (xOrO) {
            nextSquares[index] = "X";
          } else {
            nextSquares[index] = "O";
          }
          setSquares(nextSquares);
          setXOrO(!xOrO);
        };
  • Next Player를 보여주며 다음 표시를 알려준다. (ex: Next Player: X)
    return (
        <>
          <p>Next Player : {xOrO ? "X" : "O"}</p>
          <div className="board">
            <Square value={squares[0]} handleClick={() => handleSquareClick(0)} />
            <Square value={squares[1]} handleClick={() => handleSquareClick(1)} />
            <Square value={squares[2]} handleClick={() => handleSquareClick(2)} />
          </div>
        </>
      );
  • 승리조건을 충족한다면 게임을 멈추고 승리자를 보여준다. 일단 승리조건!
    • 승리조건은 가로, 세로, 대각선을 포함하여 3개가 연속적으로 존재한다면 승리조건에 만족한다. (ex: Next player ⇒ Winner player로 변경)

      const judgeWinner = (board) => {
        const winningLine = [
          [0, 1, 2],
          [3, 4, 5],
          [6, 7, 8],
          [0, 3, 6],
          [1, 4, 7],
          [2, 5, 8],
          [0, 4, 8],
          [2, 4, 6],
        ];
        for (let i = 0; i < winningLine.length; i++) {
          const [a, b, c] = winningLine[i];
          if (board[a] !== "" && board[a] === board[b] && board[a] === board[c]) {
            return board[a];
          }
        }
        return null;
      };
      
      export default judgeWinner;

      승리조건을 판단해주는 judgeWinner 함수를 생성 utils 폴더에 따로 분리해주었다.

      함수를 간단하게 설명해보자면 board 를 인자로 받아준다.

      winningLine 은 가로, 세로, 대각선을 포함하여 3개가 연속으로 존재하는 경우의수를 넣어준 배열이다.

      winningLine의 length만큼 for문을 돌아본다.

      구조분해할당으로 a,b,c를 선언해주고, 조건문을 판단한다.

      a 값이 있는지, 그리고 a와 b가 같은지 , 그리고 a와 c가 같은지

      그렇다면 a,b,c가 모두 동일한 값임으로 board[a] 를 return 해준다.

      for문을 돌고도 일치하지않으면 승리조건에 부합하지 않음으로 return null을 해준다.

  • 승리조건을 충족한다면 게임을 멈추고 승리자를 보여준다.
    const handleSquareClick = (index) => {
        const nextSquares = [...squares];
        if (squares[index] && judgeWinner(squares)) return;
        if (xOrO) {
          nextSquares[index] = "X";
        } else {
          nextSquares[index] = "O";
        }
        setSquares(nextSquares);
        setXOrO(!xOrO);
      };
  • 승리조건 충족한다면 Next player ⇒ Winner player로 변경
    return (
        <>
          {judgeWinner(squares) ? (
            `Winner : ${judgeWinner(squares)}`
          ) : (
            <p>Next Player : {xOrO ? "X" : "O"}</p>
          )}
         ~~~
        </>
      );

최종 Board.jsx

import React, { useState } from "react";
import Square from "./Square";
import judgeWinner from "./utils/judgeWinner";

const Board = () => {
  const [squares, setSquares] = useState(Array(9).fill(null));
  const [xOrO, setXOrO] = useState(true);
  console.log(squares);

  const handleSquareClick = (i) => {
    const nextSquares = [...squares];
    if (squares[i] || judgeWinner(squares)) return;
    if (xOrO) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    setSquares(nextSquares);
    setXOrO(!xOrO);
  };
  return (
    <>
      {judgeWinner(squares) ? (
        `Winner : ${judgeWinner(squares)}`
      ) : (
        <p>Next Player : {xOrO ? "X" : "O"}</p>
      )}

      <div className="board">
        <Square value={squares[0]} handleClick={() => handleSquareClick(0)} />
        <Square value={squares[1]} handleClick={() => handleSquareClick(1)} />
        <Square value={squares[2]} handleClick={() => handleSquareClick(2)} />
        <Square value={squares[3]} handleClick={() => handleSquareClick(3)} />
        <Square value={squares[4]} handleClick={() => handleSquareClick(4)} />
        <Square value={squares[5]} handleClick={() => handleSquareClick(5)} />
        <Square value={squares[6]} handleClick={() => handleSquareClick(6)} />
        <Square value={squares[7]} handleClick={() => handleSquareClick(7)} />
        <Square value={squares[8]} handleClick={() => handleSquareClick(8)} />
      </div>
    </>
  );
};

export default Board;
profile
프론트엔드 개발자 항상 뭘 하고있는 슬링

0개의 댓글