【用 AI 】零代码基础,我用 AI 15分钟搞定了一个 3D 井字棋小游戏

我的身份 / 阶段

计算机与前端零基础小白。目前处于日常学习和项目积累阶段。

我的科研 / 学习任务

尝试完成一个包含 CSS 3D 视觉动画、人机对战 AI 逻辑(优先级判断算法)以及网页响应式布局的网页小游戏制作。

我用了什么 AI 工具

ChatGPT / Claude 3.5 Sonnet

页面效果

我是怎么做的

  1. 需求输入:我向 AI 提出了明确的开发需求:“请帮我写一个单文件 HTML 网页,包含 CSS 和 JS。需要实现一个 3D 视觉风格的井字棋游戏,带有 3D 浮动动画、支持双人对战和人机对战模式切换、得分统计以及胜利判定提示。”
  2. AI 生成代码:AI 迅速吐出了完整的代码,包括使用 CSS perspective 和 transform-style: preserve-3d 实现的立体舞台,以及基于优先级的 AI 人机对战算法。
  3. 核对与测试:我直接将代码保存为 .html 文件并在浏览器打开运行。检查了落子动画(dropIn 帧动画)、人机 AI 的下棋逻辑(优先胜/阻挡/占中心/占角/随机)以及模式切换时的得分重置。

结果如何

  • 大幅提效:从零手写 3D 动画样式和交互逻辑原先可能需要花费大半天,现在通过 AI 辅助仅用 15 分钟 就完成了开发与测试。
  • 产出成果:获得了一个结构极其紧凑(仅百余行代码)、纯前端零依赖、可以直接单文件运行的 3D 井字棋网页小游戏。

踩坑和提醒

  • 3D 视角点击偏移问题:CSS 3D 旋转角度过大时,鼠标点击位置可能会出现视觉偏移,需要提醒 AI 将 perspective 视距和 rotateX 角度调整到舒适的范围。
  • 人机逻辑漏洞:AI 初版代码可能会遗漏某些赢棋分支,必须在交互中多次测试 AI 是否能正常拦截你的三连。

可复制建议

  1. 要求“单文件交付”:对于小型 demo 开发,让 AI 将 HTML、CSS、JS 整合在单文件内,极大降低调测和部署门槛。
  2. 结构化需求提示词:提要求时分点写明“UI风格 + 核心功能 + 交互细节”,AI 产出的代码质量会高很多。

代码分享

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D 井字棋</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }

  body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
    background: radial-gradient(ellipse at 50% 30%, #2b3a67 0%, #141b2d 60%, #0a0e1a 100%);
    color: #e8ecf5;
    overflow: hidden;
    perspective: 1100px;
  }

  h1 {
    font-size: 42px;
    letter-spacing: 8px;
    margin-bottom: 8px;
    text-shadow: 0 4px 18px rgba(0,0,0,.6), 0 0 30px rgba(120,160,255,.25);
    transform-style: preserve-3d;
  }
  h1 span { color: #7fb2ff; }

  #status {
    height: 34px;
    font-size: 19px;
    margin-bottom: 20px;
    letter-spacing: 2px;
    color: #c6d4f0;
    text-shadow: 0 2px 8px rgba(0,0,0,.5);
  }

  /* ---------- 3D 舞台 ---------- */
  #stage {
    transform-style: preserve-3d;
    animation: stageFloat 6s ease-in-out infinite;
  }
  @keyframes stageFloat {
    0%,100% { transform: rotateX(52deg) rotateZ(-45deg) translateZ(-10px); }
    50%     { transform: rotateX(50deg) rotateZ(-43deg) translateZ(14px); }
  }

  #board {
    width: 330px;
    height: 330px;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    gap: 10px;
    padding: 14px;
    border-radius: 22px;
    background: linear-gradient(145deg, rgba(255,255,255,.09), rgba(255,255,255,.02));
    border: 1px solid rgba(140,170,230,.28);
    box-shadow:
      0 40px 60px rgba(0,0,0,.55),
      inset 0 1px 0 rgba(255,255,255,.15);
    transform-style: preserve-3d;
    position: relative;
  }

  .cell {
    background: linear-gradient(145deg, rgba(255,255,255,.07), rgba(255,255,255,.01));
    border: 1px solid rgba(150,180,240,.16);
    border-radius: 12px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 64px;
    font-weight: 800;
    transition: background .25s, box-shadow .25s, transform .15s;
    transform-style: preserve-3d;
  }
  .cell:hover { background: rgba(130,170,255,.13); box-shadow: 0 0 18px rgba(120,160,255,.35); }

  .cell.x {
    color: #ff7d7d;
    text-shadow: 0 0 16px rgba(255,90,90,.65), 0 -4px 0 rgba(0,0,0,.25);
    animation: dropIn .45s cubic-bezier(.2,1.6,.4,1);
  }
  .cell.o {
    color: #7fd0ff;
    text-shadow: 0 0 16px rgba(80,190,255,.65), 0 -4px 0 rgba(0,0,0,.25);
    animation: dropIn .45s cubic-bezier(.2,1.6,.4,1);
  }
  @keyframes dropIn {
    from { transform: translateZ(70px) rotateX(-40deg); opacity: 0; }
    to   { transform: translateZ(0); opacity: 1; }
  }

  .cell.win {
    background: linear-gradient(145deg, rgba(255,220,120,.35), rgba(255,200,80,.15));
    box-shadow: 0 0 26px rgba(255,210,110,.8), inset 0 0 12px rgba(255,220,140,.5);
    animation: winPulse 1s ease-in-out infinite alternate;
  }
  @keyframes winPulse {
    from { transform: translateZ(0); }
    to   { transform: translateZ(24px); }
  }

  /* 棋盘厚度 */
  #board::after {
    content: "";
    position: absolute;
    inset: -2px;
    z-index: -1;
    transform: translateZ(-22px);
    border-radius: 24px;
    background: linear-gradient(160deg, #1d2742, #10182e);
  }

  /* ---------- 底部控制 ---------- */
  #panel {
    margin-top: 46px;
    display: flex;
    gap: 14px;
    align-items: center;
  }
  button {
    font-family: inherit;
    font-size: 16px;
    letter-spacing: 2px;
    padding: 11px 26px;
    border-radius: 12px;
    border: 1px solid rgba(150,180,240,.35);
    background: linear-gradient(145deg, rgba(255,255,255,.12), rgba(255,255,255,.04));
    color: #e8ecf5;
    cursor: pointer;
    transition: all .2s;
    backdrop-filter: blur(6px);
  }
  button:hover {
    background: linear-gradient(145deg, rgba(130,170,255,.3), rgba(100,140,230,.12));
    box-shadow: 0 0 20px rgba(120,160,255,.4);
    transform: translateY(-2px);
  }
  button:active { transform: translateY(1px); }

  #score {
    margin-top: 18px;
    font-size: 15px;
    letter-spacing: 1px;
    color: #9db1d8;
  }
  #score b { color: #ffd98a; }

  .hint {
    margin-top: 10px;
    font-size: 13px;
    color: #6d7fa5;
    letter-spacing: 1px;
  }
</style>
</head>
<body>

<h1>3D <span>井字棋</span></h1>
<div id="status">轮到 ✕ 玩家</div>

<div id="stage">
  <div id="board"></div>
</div>

<div id="panel">
  <button id="restart">重新开始</button>
  <button id="mode">模式:双人对战</button>
</div>
<div id="score">✕ 胜 <b>0</b> &nbsp;|&nbsp; ◯ 胜 <b>0</b> &nbsp;|&nbsp; 平局 <b>0</b></div>
<div class="hint">提示:点击格子落子 · 棋盘会轻微旋转展示立体效果</div>

<script>
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]
];

let board = Array(9).fill("");
let current = "X";
let over = false;
let vsAI = false;
let score = { X: 0, O: 0, D: 0 };

const boardEl = document.getElementById("board");
const statusEl = document.getElementById("status");
const scoreEl = document.getElementById("score");

function render() {
  boardEl.innerHTML = "";
  board.forEach((v, i) => {
    const cell = document.createElement("div");
    cell.className = "cell" + (v ? " " + v.toLowerCase() : "");
    cell.textContent = v === "X" ? "✕" : v === "O" ? "◯" : "";
    cell.onclick = () => play(i);
    boardEl.appendChild(cell);
  });
}

function play(i) {
  if (over || board[i]) return;
  board[i] = current;
  afterMove();
  if (!over && vsAI && current === "O") setTimeout(aiMove, 450);
}

function aiMove() {
  // 优先取胜 → 阻挡对手 → 中心 → 角 → 边
  const empty = board.map((v,i)=>v?-1:i).filter(i=>i>=0);
  const tryFind = (p) => {
    for (const i of empty) {
      board[i] = p;
      const win = checkWin().winner === p;
      board[i] = "";
      if (win) return i;
    }
    return -1;
  };
  let move = tryFind("O");
  if (move < 0) move = tryFind("X");
  if (move < 0 && !board[4]) move = 4;
  if (move < 0) {
    const corners = [0,2,6,8].filter(i=>!board[i]);
    move = corners.length ? corners[Math.random()*corners.length|0]
         : empty[Math.random()*empty.length|0];
  }
  board[move] = "O";
  afterMove();
}

function afterMove() {
  const { winner, line } = checkWin();
  render();
  if (winner) {
    over = true;
    score[winner]++;
    updateScore();
    statusEl.textContent = `🎉 ${winner === "X" ? "✕ 玩家" : (vsAI ? "AI" : "◯ 玩家")} 获胜!`;
    markWin(line);
  } else if (board.every(Boolean)) {
    over = true;
    score.D++;
    updateScore();
    statusEl.textContent = "平局!再来一局?";
  } else {
    current = current === "X" ? "O" : "X";
    statusEl.textContent = `轮到 ${current === "X" ? "✕" : "◯"} 玩家`;
  }
}

function checkWin() {
  for (const l of LINES) {
    const [a,b,c] = l;
    if (board[a] && board[a] === board[b] && board[a] === board[c])
      return { winner: board[a], line: l };
  }
  return { winner: null, line: null };
}

function markWin(line) {
  if (!line) return;
  [...boardEl.children].forEach((c,i)=>{
    if (line.includes(i)) c.classList.add("win");
  });
}

function restart() {
  board = Array(9).fill("");
  current = "X";
  over = false;
  statusEl.textContent = "轮到 ✕ 玩家";
  render();
}

function updateScore() {
  scoreEl.innerHTML =
    `✕ 胜 <b>${score.X}</b> &nbsp;|&nbsp; ◯ 胜 <b>${score.O}</b> &nbsp;|&nbsp; 平局 <b>${score.D}</b>`;
}

document.getElementById("restart").onclick = restart;
document.getElementById("mode").onclick = function () {
  vsAI = !vsAI;
  this.textContent = vsAI ? "模式:人机对战" : "模式:双人对战";
  score = { X: 0, O: 0, D: 0 };
  updateScore();
  restart();
};

render();
</script>
</body>
</html>

如果有问题可以评论区问我哈,大家一起学习一起进步。