본문 바로가기
chatGPT와 인공지능AI

1인용 오목게임(vs컴)

by Cray Fall 2025. 5. 15.

컴퓨터와 대전할 수 있는 아주 세지 않고 약간 상대가 되어주는 오목입니다.
은근히 잘 둔다니까요.

chatGPT에게 시켜서 제작된 코드인데 꽤 괜찮네요 ㅎ

티스토리에서는 아쉽게도 게임오버 후 재시작 버튼이 작동이 안되네요 ㅎ
아래 화면에서 직접 플레이 가능합니다.

오목 게임 (플레이어 vs AI)

 
 

단일코드인데 소스코드 공유 드려요~

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>오목 게임 vs AI</title>
  <style>
    body {
      margin: 0;
      font-family: sans-serif;
      background-color: #f9f9f9;
    }
    canvas {
      display: block;
      margin: 30px auto;
      border: 2px solid #000;
      background-color: #fff;
    }
    #result-message {
      display: none;
      position: fixed;
      top: 40%;
      left: 50%;
      transform: translate(-50%, -50%);
      font-size: 64px;
      font-weight: bold;
      color: #fff;
      background: rgba(0, 0, 0, 0.7);
      padding: 30px 60px;
      border-radius: 20px;
      z-index: 10;
      text-align: center;
    }
    #restart-btn {
      display: none;
      position: fixed;
      top: 60%;
      left: 50%;
      transform: translate(-50%, -50%);
      font-size: 24px;
      padding: 15px 30px;
      background-color: #28a745;
      color: white;
      border: none;
      border-radius: 10px;
      cursor: pointer;
      z-index: 10;
    }
    #restart-btn:hover {
      background-color: #218838;
    }
    #particles-js {
      position: fixed;
      top: 0; left: 0;
      width: 100%;
      height: 100%;
      z-index: -1;
    }
  </style>
</head>
<body>
  <h2 style="text-align: center;">오목 게임 (플레이어 vs AI)</h2>
  <canvas id="board" width="600" height="600"></canvas>
  <div id="result-message"></div>
  <button id="restart-btn" onclick="restartGame()">다시 시작</button>
  <div id="particles-js"></div>

  <!-- particles.js CDN -->
  <script src="https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js"></script>

  <script>
    const canvas = document.getElementById("board");
    const ctx = canvas.getContext("2d");
    const size = 15;
    const cell = canvas.width / size;
    let board = Array.from({ length: size }, () => Array(size).fill(0));
    let gameEnded = false;

    drawBoard();

    canvas.addEventListener("click", (e) => {
      if (gameEnded) return;

      const x = Math.floor(e.offsetX / cell);
      const y = Math.floor(e.offsetY / cell);

      if (board[y][x] === 0) {
        placeStone(x, y, 1);
        if (checkWin(x, y, 1)) {
          showResult("🎉 플레이어 승리!");
          return;
        }

        setTimeout(() => {
          aiMove();
        }, 300);
      }
    });

    function drawBoard() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      for (let i = 0; i < size; i++) {
        ctx.beginPath();
        ctx.moveTo(cell / 2, cell / 2 + i * cell);
        ctx.lineTo(canvas.width - cell / 2, cell / 2 + i * cell);
        ctx.stroke();

        ctx.beginPath();
        ctx.moveTo(cell / 2 + i * cell, cell / 2);
        ctx.lineTo(cell / 2 + i * cell, canvas.height - cell / 2);
        ctx.stroke();
      }
    }

    function placeStone(x, y, player) {
      board[y][x] = player;
      ctx.beginPath();
      ctx.arc(x * cell + cell / 2, y * cell + cell / 2, cell / 2.5, 0, Math.PI * 2);
      ctx.fillStyle = player === 1 ? "black" : "white";
      ctx.fill();
      ctx.stroke();
    }

    function checkWin(x, y, player) {
      const directions = [[1,0],[0,1],[1,1],[1,-1]];
      for (let [dx, dy] of directions) {
        let count = 1;
        for (let dir of [-1, 1]) {
          let nx = x, ny = y;
          while (true) {
            nx += dx * dir;
            ny += dy * dir;
            if (nx < 0 || ny < 0 || nx >= size || ny >= size || board[ny][nx] !== player) break;
            count++;
          }
        }
        if (count >= 5) return true;
      }
      return false;
    }

    function aiMove() {
      let maxScore = 0;
      let bestMove = null;

      for (let y = 0; y < size; y++) {
        for (let x = 0; x < size; x++) {
          if (board[y][x] !== 0) continue;

          const attackScore = evaluate(x, y, 2);
          const defenseScore = evaluate(x, y, 1);
          const score = Math.max(attackScore, defenseScore * 0.9);

          if (score > maxScore) {
            maxScore = score;
            bestMove = [x, y];
          }
        }
      }

      if (bestMove) {
        const [x, y] = bestMove;
        placeStone(x, y, 2);
        if (checkWin(x, y, 2)) {
          showResult("💥 AI 승리!");
        }
      } else {
        showResult("무승부!");
      }
    }

    function evaluate(x, y, player) {
      let score = 0;
      const directions = [[1,0],[0,1],[1,1],[1,-1]];

      for (let [dx, dy] of directions) {
        let count = 1;
        let block = 0;

        for (let i = 1; i < 5; i++) {
          const nx = x + dx * i, ny = y + dy * i;
          if (nx < 0 || ny < 0 || nx >= size || ny >= size) break;
          if (board[ny][nx] === player) count++;
          else if (board[ny][nx] !== 0) { block++; break; }
          else break;
        }

        for (let i = 1; i < 5; i++) {
          const nx = x - dx * i, ny = y - dy * i;
          if (nx < 0 || ny < 0 || nx >= size || ny >= size) break;
          if (board[ny][nx] === player) count++;
          else if (board[ny][nx] !== 0) { block++; break; }
          else break;
        }

        if (count >= 5) return 1000000;

        if (block === 0) {
          if (count === 4) score += 100000;
          else if (count === 3) score += 1000;
          else if (count === 2) score += 100;
          else if (count === 1) score += 10;
        } else if (block === 1) {
          if (count === 4) score += 10000;
          else if (count === 3) score += 500;
          else if (count === 2) score += 50;
          else if (count === 1) score += 5;
        }
      }
      return score;
    }

    function showResult(message) {
      gameEnded = true;
      const resultDiv = document.getElementById("result-message");
      const restartBtn = document.getElementById("restart-btn");

      resultDiv.innerText = message;
      resultDiv.style.display = "block";
      restartBtn.style.display = "block";

      particlesJS("particles-js", {
        "particles": {
          "number": { "value": 100 },
          "color": { "value": "#ffffff" },
          "shape": { "type": "circle" },
          "opacity": { "value": 0.6 },
          "size": { "value": 4 },
          "move": { "enable": true, "speed": 3, "out_mode": "out" }
        },
        "interactivity": { "detect_on": "canvas" },
        "retina_detect": true
      });
    }

    function restartGame() {
      board = Array.from({ length: size }, () => Array(size).fill(0));
      gameEnded = false;
      drawBoard();

      // Clear overlays
      document.getElementById("result-message").style.display = "none";
      document.getElementById("restart-btn").style.display = "none";
      document.getElementById("particles-js").innerHTML = "";
    }
  </script>
</body>
</html>

오늘도 방문해주신 모든 분들께 감~솨드립니다!