*참고자료 : 자습서: 틱택토 게임 – React
게임 방법:
- 준비: 3x3 크기의 격자판을 준비합니다.
- 플레이어: 두 명의 플레이어가 필요하며, 한 명은 X, 다른 한 명은 O를 선택합니다.
- 번갈아 가며 놓기: 첫 번째 플레이어가 빈 칸에 자신의 기호를 놓습니다. 이후 두 번째 플레이어도 빈 칸에 자신의 기호를 놓습니다. 이렇게 번갈아 가며 진행합니다.
- 승리 조건: 가로, 세로, 또는 대각선 중 아무 곳이든 자신의 기호를 세 개 연속으로 놓으면 승리합니다.
- 무승부: 격자판이 모두 채워졌는데도 승자가 없으면 무승부입니다.
* type 정의
type Player = 'X' | 'O' | null
type BoardState = Player[]
type GameHistory = BoardState[]
위 게임을 구현하는 데 있어서 저는 Square, Board, Game 이렇게 총 3개의 컴포넌트를 활용하였습니다.
Square 컴포넌트는 게임 보드의 한 칸을 의미하며 (버튼 태그 하나를 랜더링함. ), value prop으로 해당 칸의 값 (X,O,null)을 받습니다. 또한 해당 칸을 클릭했을 때 실행될 함수를 (onSquareClick 함수를 통해) 받습니다.
interface SquareProps {
value: Player
onSquareClick: () => void
}
function Square({ value, onSquareClick }: SquareProps) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
)
}
Board 컴포넌트는 전체 게임 보드를 나타냅니다.
interface BoardProps {
xIsNext: boolean
squares: BoardState
onPlay: (nextSquares: BoardState) => void
}
xIsNext prop을 받아, 현재 플레이어가 X인지 O인지 알려줍니다. 또한 squares prop을 통해 현재 게임 보드의 상태를 받습니다. handleClick 함수에서 사용자가 클릭한 칸의 인덱스를 받아 해당 칸의 값을 X 또는 O로 변경하고, onPlay 함수를 호출하여 상위 컴포넌트에 변경된 게임 보드 상태를 전달합니다. 승자가 결정되었거나 해당 칸이 채워졌다면 더 이상 클릭할 수 없도록 처리합니다. 현재 플레이어와 승자 정보를 화면에 표시합니다. Square 컴포넌트를 9개 렌더링하여 전체 게임 보드를 구성합니다.
- xIsNext: 이 prop은 현재 누가 플레이어인지를 나타냅니다. true이면 X 플레이어, false이면 O 플레이어입니다.
- squares: 이 prop은 현재 게임 보드의 상태를 나타내는 배열입니다.
- onPlay: 이 prop은 자식 컴포넌트인 Board에서 플레이 버튼이 클릭되었을 때 실행될 함수입니다. 이 함수는 부모 컴포넌트에서 정의된 handlePlay 함수입니다.
이렇게 부모 컴포넌트에서 자식 컴포넌트인 Board에 필요한 데이터와 이벤트 핸들러 함수를 전달하고 있습니다.
- xIsNext와 squares는 게임 상태를 표현하는 데이터입니다. Board 컴포넌트는 이 데이터를 사용하여 게임 보드를 렌더링합니다. (필요한 데이터를 부모 컴포넌트로부터 전달받음. )
- onPlay 함수는 Board 컴포넌트에서 플레이 버튼이 클릭되었을 때 호출되는 콜백 함수입니다. 이 함수는 부모 컴포넌트에서 정의된 handlePlay 함수이며, 새로운 게임 상태를 업데이트하는 역할을 합니다.
이와 같이 부모 컴포넌트는 자식 컴포넌트에게 필요한 데이터와 이벤트 핸들러 함수를 전달함으로써, 자식 컴포넌트가 부모 컴포넌트의 상태를 업데이트할 수 있게 합니다.
function Board({ xIsNext, squares, onPlay }: BoardProps) {
function handleClick(i: number) {
if (calculateWinner(squares) || squares[i]) return
const nextSquares = squares.slice()
nextSquares[i] = xIsNext ? 'X' : 'O'
onPlay(nextSquares)
}
const winner = calculateWinner(squares)
const status = winner
? `Winner: ${winner}`
: `Next player: ${xIsNext ? 'X' : 'O'}`
return (
<>
<div className="dark:bg-slate-800 dark:text-white">
<div className="status text-bold text-blue-300 text-xl underline">
{status}
</div>
<div className=" text-white">
{[0, 1, 2].map((row) => (
<div key={row} className="board-row">
{[0, 1, 2].map((col) => {
const index = row * 3 + col
return (
<Square
key={index}
value={squares[index]}
onSquareClick={() => handleClick(index)}
/>
)
})}
</div>
))}
</div>
</div>
</>
)
}
Game 컴포넌트는 전체 게임 로직을 관리합니다. useState 훅을 사용하여 게임 상태를 관리합니다.
history 상태에는 게임 보드의 모든 변경 이력이 저장됩니다.
currentMove 상태에는 현재 보여지고 있는 게임 보드의 인덱스가 저장됩니다.
handlePlay 함수에서 Board 컴포넌트로부터 전달받은 다음 게임 보드 상태를 history에 추가하고, currentMove를 업데이트합니다.
jumpTo 함수에서 사용자가 선택한 이전 게임 상태로 currentMove를 업데이트합니다.
이전 게임 상태로 이동할 수 있는 버튼 목록을 렌더링합니다.
Board 컴포넌트를 렌더링하고, xIsNext, currentSquares, handlePlay 함수를 props로 전달합니다.
function Game() {
const [history, setHistory] = useState<GameHistory>([Array(9).fill(null)])
const [currentMove, setCurrentMove] = useState(0)
const xIsNext = currentMove % 2 === 0
const currentSquares = history[currentMove]
function handlePlay(nextSquares: BoardState) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]
setHistory(nextHistory)
setCurrentMove(nextHistory.length - 1)
}
function jumpTo(move: number) {
setCurrentMove(move)
}
const moves = history.map((_, move) => (
<li key={move}>
<button onClick={() => jumpTo(move)}>
{move ? `Go to move #${move}` : 'Go to game start'}
</button>
</li>
))
return (
<>
<div className="container">
<div className="flex flex-row justify-center gap-2">
<p className="title text-7xl text-[#1e40af]">
TIC-TAC-TOE GAME
</p>
<div className="animal w-[200px] h-[100px]"></div>
</div>
<div className="game w-[100%] h-[100%] flex flex-row justify-center">
<div className="mt-10 w-[300px] h-[300px] border-blue-800 border-[4px] photo"></div>
<div className="game-board ml-20">
<Board
xIsNext={xIsNext}
squares={currentSquares}
onPlay={handlePlay}
/>
</div>
<div className="game-info text-gray-400 whitespace-nowrap">
<ol>{moves}</ol>
</div>
</div>
</div>
</>
)
}
+ calculateWinner 함수
function calculateWinner(squares: BoardState): Player {
const lines = [
[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 [a, b, c] of lines) {
if (
squares[a] &&
squares[a] === squares[b] &&
squares[a] === squares[c]
) {
return squares[a]
}
}
return null
}
* 실제 구현한 웹사이트
[project] 틱택토 게임 구현해보기 (tic-tac-toe) (tistory.com)
[project] 틱택토 게임 구현해보기 (tic-tac-toe)
tic-tac-toe-pied-six.vercel.app https://tic-tac-toe-pied-six.vercel.app/ tic-tac-toe-pied-six.vercel.app * 참고자료 : 자습서: 틱택토 게임 – React
with-mimi.tistory.com
'[프론트] 포트폴리오' 카테고리의 다른 글
[project] 틱택토 게임 구현해보기 (tic-tac-toe) (1) | 2024.07.25 |
---|---|
[project-final] 나만의 포트폴리오 웹사이트 만들기 (1) | 2024.07.24 |
[project] 투두리스트 구현해보기 (1) | 2024.07.24 |
[project] 로그인 화면 구현해보기 (1) | 2024.07.24 |
[project] 계산기 웹사이트 만들어보기 (2) | 2024.06.09 |