첫 프로젝트는 틱택토로 선정하게되었다. 이유는 리액트 공식문서의 튜토리얼이여서 한번 봐본적이있고, 간단해보여서였다.
이후 틱택토와 비슷한 UI 및 로직인 오목을 구현해보고, 한단계 더 나아가 오델로도 구현해 볼 예정이다.
먼저, 틱택토 간단할줄알았는데.......... 안보고 구현하니 막막했다.
그래서 처음엔 안보고하려다 squares라는 배열을 만드는 것 부터 못했어서... 리액트 공식문서 (리액트 번역스터디 페이지) 를 참고해서 한번 따라 만들어보고 -> 안보고 구현해보다가 막힌부분만 다시 보고 구현해보고 -> 페어프로그래밍으로 같이 구현해보고 -> 이렇게 한 3~4번을 구현해보니 이제서야 이부분 만큼은 안보고 할 수준이 되어서 블로그 글을 작성하게 되었다.
추가구현사항인 이차배열로 만들어보는 부분은 아직도 막막하지만, 이부분도 이런식으로 세네번 반복해보면 잘할수 있지않을까.. 라는 맘으로 공부를 해야겠다!
공통 구현사항 간단하게 구현해보기 (실제 구현 흐름에 맞춰서 글 작성)
구현사항
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;
}
먼저 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);
};
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);
};
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;