ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JavaScript 입문 : 컴퓨터와 함께하는 틱택토 게임 (TicTacToe)
    컴퓨터 알아가기/JavaScript 2022. 8. 25. 19:30
    728x90
    반응형

    지난시간까지 만들어 본 틱택토 게임은 두사람이 하나의 화면에서 직접 입력하면서 승부를 가리는 방법을 공부해 봤는데요.. 아직 실력은 미천하나 다른 강좌의 도움을 받아 인간과 컴퓨터의 단순 틱택토 게임을 공부해 보았습니다. 

     

    관련 과정은 freeCodeCamp.org에서 참조 하였습니다. 

     

    참조강좌

     

    결과물은 다음과 같이 나오더군요. 

     

     

     

    과정은 지난 틱택토에서 많이 이야기를 하였기 때문에 HTML과 CSS 그리고 JS에 필요 내용들을 주석 처리했습니다. 

    특이한 점은 여러가지 방법으로 9칸에 대한 인덱스 처리를 하고 이차원 배열을 이용하지 않고도 할 수 있는 내용을 볼 수 있었습니다. 

     

    1. HTML

     

    HTML에서는 자바스크립트에서 메소드로 만들지 않고 직접 3 by 3 테이블을 지정하도록 합니다. 이는 CSS에서 좀 더 틱택토 모양으로 하기 위해서 미리 만들어 둔 것이라고 봅니다. 

     

    <!DOCTYPE html>
    <html lang="ko">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>TicTacToe_Deep</title>
      <link rel="stylesheet" href="ttt.css">
    </head>
    <body>
    
      <table>
        <tr>
          <td class="cell" id="0"></td>
          <td class="cell" id="1"></td>
          <td class="cell" id="2"></td>      
        </tr>
        <tr>
          <td class="cell" id="3"></td>
          <td class="cell" id="4"></td>
          <td class="cell" id="5"></td>      
        </tr>
        <tr>
          <td class="cell" id="6"></td>
          <td class="cell" id="7"></td>
          <td class="cell" id="8"></td>      
        </tr>
      </table>
      <div class="endgame">
        <div class="text"></div>
      </div>
      <button onclick="startGame()">Replay</button>
      
      <script src="ttt.js"></script>
    </body>
    </html>

     

    2. CSS

     

    기본적인 CSS 효과를 위해 작업을 미리 해 둡니다. 특히 게임이 끝났을 경우 화면에 결과가 표시될 수 있게 미리 지정을 하고 필요시 자바스크립트에서 보정을 할 수 있도록 했습니다. 

     

    td {
      border: 2px solid #333;
      height: 100px;
      width: 100px;
      text-align: center;
      vertical-align: middle;
      font-family: "Comic Sans MS", cursive, sans-serif;
      font-size: 60px;
      cursor: pointer;
    }
    
    table {
      border-collapse: collapse; /* 선없애기 */
      position: absolute; /* 절대위치 */
      /* 중앙 위치 */
      left: 50%;
      margin-left: -155px;
      top: 50px; /* 상단 약간 위치 조정 */
    }
    
    table tr:first-child td { /* 첫번째 row 3칸, first Child */
      border-top: 0; /* top 줄 없앰 */
    }  
    table tr:last-child td { /* 마지막 row 3칸, last Child */
      border-bottom: 0; /* bottom 줄 없앰 */
    }  
    table tr td:first-child { /* 왼쪽 column 3칸, 각 td의 first Child */
      border-left: 0; /* 왼쪽줄 없앰 */
    }  
    table tr td:last-child { /* 마지막 column 3칸, last Child */
      border-right: 0; /* 오른쪽줄 없앰 */
    }  
    
    .endgame { /* endgame 중앙에 표시 */
      display: none; 
      width: 200px;
      top: 120px;
      background-color: rgba(205, 133, 63, 0.8);
      position: absolute;
      left: 50%;
      margin-left: -100px;
      padding-top: 50px;
      padding-bottom: 50px;
      text-align: center;
      border-radius: 5px;
      color: white;
      font-size: 2em;
    }

     

    3. JavaScript 

     

    역시 자바스크립트에는 많이 사용되는 메소드를 적용하였습니다. 한줄 한줄 공부하면서 필요한 메소드는 별도로 정리하여 연습하는 과정을 가졌습니다. 

     

    이 자바스크립트를 이해한 후 향후에 진짜 AI와 게임을 하는 방법을 공부하고자 합니다. 이번에는 AI없이 두뇌없는 컴퓨터와 게임을 하지만 Minmax Algorithm을 통해 좀 더 똑똑한 컴퓨터와 게임을 하는게 최종 틱택토 게임의 마지막이 아닐까 생각을 합니다. 이 페이지에서는 단순한 컴퓨터와의 게임입니다. 

     

    let originBoard; // 9개칸에는 X, O, Nothing 
    const manPlayer = 'O';
    const aiPlayer = 'X';
    const winCase = [ // 승리조합 
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
      [0, 3, 6],
      [1, 4, 7],
      [2, 5, 8],
      [0, 4, 8],
      [2, 4, 6],
    ]
    
    // 전체 td cell 활성화 
    const cells = document.querySelectorAll('.cell');
    
    startGame();
    
    function startGame() {
      document.querySelector('.endgame').style.display = 'none'; //게임시작시 none 
      // originBoard에 key값을 배열로
      originBoard = Array.from(Array(9).keys());
      // console.log(originBoard); 
      for (let i = 0; i < cells.length; i++) {
        cells[i].textContent = ''; // cell에 아무것도 없게
        cells[i].style.removeProperty('background-color'); // 승리 색상 -> 게임시작시 없앰
        cells[i].addEventListener('click', turnClick, false);
      }
    }
    
    function turnClick(event) { // 사람이 플레이하는 코딩먼저하고 작동되면 AI turn
      // console.log(event.target.id);
    
      // 사람 또는 AI가 하는경우는 각 칸이 숫자인 경우(문자인 경우는 누군가 플레이 완료)
      if (typeof originBoard[event.target.id] === 'number') {
        turn(event.target.id, manPlayer); // 사람이 플레이하는 함수
        if (!checkTie()) turn(aiSpot(), aiPlayer) // 동점이 아닌경우, aiSpot을 통해 id구함
      }
    }
    
    function turn(eventId, player) { // turn 함수정의
      originBoard[eventId] = player;
      document.getElementById(eventId).textContent = player; // manPlayer 내역입력
      let gameWon = checkWin(originBoard, player);
      if (gameWon) gameOver(gameWon) // 게임결과
    }
    
    function checkWin(board, player) { // board는 게임을 진행하는 새로운 board
      // reduce메소드를 통하여 board의 모든 곳을 찾는 방법
      let plays = board.reduce((a, e, i) =>  // accumulate, element, index, 하나결과값
        (e === player) ? a.concat(i) : a, []); // e가 player 같으면 acc에 index를 연결하고
        //다르면 acc에 빈배열
      gameWon = null;
      for (let [index, win] of winCase.entries()) { // 하나씩 winCase를 인덱스와 요소를 loop
          if (win.every(e => plays.indexOf(e) > -1)) { // 모든 요소가 보드 plays하는데 -1보다 크면 
            gameWon = {index: index, player: player}; // winner와 player
            break;
          } 
      }
      return gameWon;
    }
    
    function gameOver(gameWon) { // 승부가 나면 
      // 승리된 칸들을 하이라이트 시키고 No more click
      for (let index of winCase[gameWon.index]) { // 승리한 인덱스 loop
        document.getElementById(index).style.backgroundColor = // 변수를 id로?
        gameWon.player === manPlayer ? 'blue' : 'red';
      }
      for (let i = 0; i < cells.length; i++) {
        cells[i].removeEventListener('click', turnClick, false);
      }
      declareWinner(gameWon.player == manPlayer ? 'You Win!' : 'You Lose.');
    }
    
    function declareWinner(who) {
      document.querySelector('.endgame').style.display = 'block';
      document.querySelector('.endgame .text').textContent = who;
    }
    
    function emptySquares() { // filter 메소드 활용 
      return originBoard.filter( value => typeof value == 'number'); // number면 빈칸
    }
    
    function aiSpot() { // ai의 target.id 구하는 함수
      return emptySquares()[0]; // 빈 사각형을 찾고 첫번째 요소를 가져옴
    }
    
    function checkTie() {
      if (emptySquares().length == 0) { // 동점은 빈칸의 길이가 0
        for(let i = 0; i < cells.lenghth; i++) {
          cells[i].style.backgroundColor = 'green';
          cells[i].removeEventListener('click', turnClick, false); // 클릭막음
        }
        declareWinner("Draw") // 무승부
        return true;
      }
      return false;
    }

     

    좀 오래 걸렸지만 재미도 있었네요. 

     

    단지, 에러를 하나 발견했는데 비기는 경우는 9칸이 전부 채워졌다라는 조건하에 코딩을 했는데 최종적으로 사람이 이겨도 무승부로 나오는 경우가 발생이 되었습니다. 이 부분은 보강이 필요한 듯 합니다. 개발자에게 문의를 해보았지만 아직 답은 없네요.

     

    아래와 같은 경우 사람이 이겼는데 9칸이 다 찼다고 무승부로 나옵니다. 다른 코드를 적용할려고 해도 어렵네요.....

     

     

     

    직접 실행결과를 코드펜에 옮겼습니다. 

     

     

    See the Pen TicTacToe_ex by skkim0303 (@skkim0303) on CodePen.

    반응형

    댓글

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
Designed by Tistory.