리액트 틱택토 튜토리얼 문서 읽어보기전에 혼자서 미리 만들어봤는데요
설계없이 뇌를 빼고 만든거라 버벅버벅,,, 2시간 걸렸네요
심지어 history 기능에 오류 있음 ^_^
리액트 틱택토 튜토리얼 문서 링크
지식 쇼핑, 강의 결제, 책 구매 읽기 = 과학자, 이론가적인 접근
실제 코딩은 엔지니어링/기술에 가깝다.
그만 지식 쇼핑하고 코딩 근육 키워보자..
리액트 틱택토 개발 계획

- Board 필요함 개별 칸(Cell)들은 [Empty, X, O] 3가지중 1개의 값을 가짐
- Board 클릭시 현재 Turn(O | X)의 블럭이 생김 ⇒ Win, Lose 판별 필요
- History 버튼 누르면 해당 History로 롤백됨
- 승리 판별 로직은 배열 정보로 확인 가능
결론, 후기
눈에 딱 보이는 데이터 구조만 생각하고, 로직 개략적으로 구상하고 IDE 바로 켜서 코딩 시작
1. 설계가 필요함. 중간에 내 생각대로 기능 작동 안되니까 코드가 덕지덕지 생김
2. 기능 작동 우선이 되어서 되는대로 개발하니까 중반부터는 기존에 작성했던 코드 의미가 다 없어짐(오히려 방해되는..)
const WIN_CONDITION = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
]
const X_CELL = 1
const O_CELL = 2
// 승리 판단해서 겜 종료할지 판단
// 승리조건 어떻게 판단하지 array로 판단?
export function isGameOver(board:number[]){
const ocupiedXCells = board.map((item,idx)=> item === X_CELL? idx : null).filter(val => val !== null)
const ocupiedOCells = board.map((item,idx)=> item === O_CELL? idx : null).filter(val => val !== null)
for (const wCondition of WIN_CONDITION){
// newBoard에서 현재 turn이 차지하고 있는 칸 index를 가져오고싶음
const XWin = ocupiedXCells.toString() === wCondition.toString()
const OWin = ocupiedOCells.toString() === wCondition.toString()
console.log("-------", wCondition,ocupiedXCells, ocupiedOCells)
console.log(XWin,OWin)
// console.log(wCondition.toString(), ocupiedXCells.toString(),nowWin)
if( XWin ) return X_CELL;
if( OWin ) return O_CELL
}
return 0;
}
// src/app/page.tsx
'use client'
import { useState } from "react";
import { isGameOver } from "./core";
const DEFAULT_BOARD= [0,0,0, 0,0,0, 0,0,0];
const DEFAULT_HISTORY = [{ board: DEFAULT_BOARD }]
const EMPTY_CELL = 0
const X_CELL = 1
const O_CELL = 2
export default function Home() {
const [turn, setTurn] = useState('X')
const [board, setBoard] = useState(DEFAULT_BOARD)
const [history, setHistory] = useState(DEFAULT_HISTORY)
const onClickCell = (cell: any, idx: any) =>{
// 누를 수 있는 칸인지 판단
if (cell !== EMPTY_CELL) {
// alert("님 반칙 하지마셈") // 방해됨
return
}
const whoWins = isGameOver(board)
console.log(whoWins, 'whoWins')
if(whoWins === X_CELL) {
alert("X Winner")
return;
}else if(whoWins === O_CELL){
alert("O Winner")
return;
}
// 누를 수 있다면 board에 반영
const newCell = turn === 'X' ? X_CELL : O_CELL;
const newBoard = board.map((item, i) => i===idx ? newCell : item)
// item === newCell 해도 같긴한데 너무 임시 방편으로 떼운것 같음.
const whoWinsNow = isGameOver(newBoard)
setBoard(newBoard) //
setTurn(prev=> prev === 'X' ? 'O' : 'X')
if(whoWinsNow === X_CELL) {
alert("X Winner")
return;
}else if(whoWinsNow === O_CELL){
alert("O Winner")
return;
}
// 위 내용을 히스토리에 기록
const totalTurn = newBoard.filter(x => x === X_CELL || x===O_CELL).length;
let newHistory;
if (history.length > totalTurn){
newHistory = [...history.slice(0,totalTurn), {board: newBoard}]
}else{
newHistory = [...history, {board: newBoard}]
}
setHistory(newHistory)
}
return (
<div className='h-screen flex justify-center items-center'>
<div>
<h1 className='text-2xl font-bold text-blue-600 pb-8'>React 틱택토</h1>
<div className='flex justify-center'>
<div className='pr-16'>
<div className='pb-2'>Next Player: {turn}</div>
<div className='grid grid-cols-3 text-center'>
{board.map((cell, idx)=>{
// 보드에 cell 상태 정보 표현
let cell_str = '';
if(cell === EMPTY_CELL) cell_str = ''
if(cell === X_CELL) cell_str = 'X'
if(cell === O_CELL) cell_str = 'O'
// onClick 이벤트는 어떻게 하지
return <div className='w-8 h-8 border' onClick={()=>onClickCell(cell, idx)}>{cell_str}</div>
})}
</div>
</div>
<div className='flex flex-col'>
{/* <div>1. <button onClick={()=>alert("Hi")}>Go to game start</button></div> */}
{
history.map((eachHistory, idx) => <div className='flex justify-center '>
<span>
{idx+1}.
</span>
<button onClick={()=>setBoard(eachHistory.board)}>
Go to move {idx}
</button>
</div>)
}
</div>
</div>
</div>
</div>
);
}
일단 만들어봄. 작동은 되고, 약간의 오류 ^_^가 있지만 넘어가겠음
코드 리뷰, 반성 포인트
calculateWinner : 아 정답 배열의 index를 그냥 콕 집어서 쓰면 되네
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c])
근데 나는 번거롭게 필터링을 해버림..
const ocupiedXCells = board
.map((item,idx)=> item === X_CELL? idx : null)
.filter(val => val !== null)
const ocupiedOCells = board
.map((item,idx)=> item === O_CELL? idx : null)
.filter(val => val !== null)
심지어 이렇게 가져온 값을
const XWin = ocupiedXCells.toString() === wCondition.toString()
const OWin = ocupiedOCells.toString() === wCondition.toString()
비교해버리는데,,, (1)굳이임
(2) currentBoard, nextBoard로 나뉘게 된 원인임.
설계가 절실하다. 애매하게 로직, 상태값만 생각하고 바로 코딩 들어가니 턱턱 막히는거지