리액트 틱택토 NextJS 혼자 코딩 내맘대로 만들어본거

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

 

 

 

리액트 틱택토 개발 계획

리액트 틱택토 NextJS 코딩

  1. Board 필요함 개별 칸(Cell)들은 [Empty, X, O] 3가지중 1개의 값을 가짐
  2. Board 클릭시 현재 Turn(O | X)의 블럭이 생김 ⇒ Win, Lose 판별 필요
  3. History 버튼 누르면 해당 History로 롤백됨
  4. 승리 판별 로직은 배열 정보로 확인 가능

 

 

 

결론, 후기

눈에 딱 보이는 데이터 구조만 생각하고, 로직 개략적으로 구상하고 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로 나뉘게 된 원인임.

설계가 절실하다. 애매하게 로직, 상태값만 생각하고 바로 코딩 들어가니 턱턱 막히는거지

 

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다