第一次写网页游戏,选猜数字最合适:规则一句话能讲清,却把输入、状态、反馈、重置这些所有游戏都离不开的环节全部用上了。读完这篇,你会得到一个浏览器打开就能玩的猜数字小游戏,顺便真正打通 HTML、CSS、JavaScript 三者之间的协作方式。

1. 游戏规则

电脑在心里想一个 1~100 的整数,你每次在输入框里猜一个数。猜小了,页面提示"太小了";猜大了,提示"太大了";猜中,游戏结束,并显示你一共猜了几次。想重玩,点"重新开始"按钮即可。

别小看这套流程,它几乎包含了一个游戏的全部要素:游戏状态(答案、已猜次数)、玩家操作(输入数字、点击按钮)、系统反馈(提示文字)、以及重置机制。很多复杂的游戏,本质上也只是在回答这四个问题。我们先把规则量化清楚,再动手写代码,思路会顺很多:

顺便说个数学小知识:因为每次反馈都能排除大约一半的可能,用"二分法"猜 1~100 的数字最多 7 次必中。这也是为什么这个游戏适合入门——它天然自带一个最优策略,你可以边玩边验证自己的猜测是不是太"随缘"了。

2. 界面搭建

先写 HTML 结构。我们不需要任何框架,一个 div 容器、一个数字输入框、两个按钮、两块"状态显示屏"就够用。注意给每个要被 JavaScript 操作的元素加上 id,这是 DOM 操作的地基。

<div class="game">
  <h1>猜数字</h1>
  <p>我随机想了一个 1~100 的整数,你能猜中吗?</p>
  <input type="number" id="guess" min="1" max="100" placeholder="输入你的猜测">
  <button id="submit">猜!</button>
  <button id="restart">重新开始</button>
  <p id="feedback">等待你的第一次猜测……</p>
  <p id="count">已猜次数:0</p>
</div>

几个细节值得说明:第一,type="number" 会让浏览器弹出数字键盘,min 和 max 虽然不能阻止用户手动输入越界值,但给了用户明确的输入预期;第二,#feedback 负责显示"太大/太小/恭喜"等核心反馈,#count 负责显示次数,两者分工明确,后面 JavaScript 只需要改它们的 textContent 就能"显示状态"。

把这个结构放进 <body>,用浏览器打开,你已经能看到一个"不能动"的界面了。这就是前端开发的第一步:先有骨架,再注入灵魂。

3. 样式美化

默认的浏览器样式太素,我们用一点点 CSS 让它像个正经游戏:卡片居中、圆角、阴影、按钮悬停变色。CSS 不是这篇的重点,但一个"好看"的界面会让调试心情好很多。

body {
  font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
  background: #f0f4f8;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}
.game {
  background: #fff;
  padding: 2.5rem;
  border-radius: 16px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
  text-align: center;
  width: 320px;
}
input, button {
  font-size: 1rem;
  padding: 0.5rem 0.8rem;
  border-radius: 8px;
  border: 1px solid #ccc;
  margin: 0.3rem;
}
button {
  background: #4f6ef7;
  color: #fff;
  border: none;
  cursor: pointer;
}
button:hover { background: #3d5ae0; }
#feedback {
  font-size: 1.1rem;
  font-weight: bold;
  min-height: 1.5em;
}

这里用 display: flex 配合 justify-content: center 和 align-items: center 让卡片在页面上完美居中,min-height: 100vh 保证窗口无论多高都能垂直居中。#feedback 设置 min-height,是为了防止文字从一行变两行时页面上下跳动。

把这段 CSS 放进 <style> 标签,刷新页面,游戏卡片就"立"起来了。接下来是重头戏:写逻辑。

4. 逻辑实现

游戏的核心逻辑分两块:一是"猜一次"的处理函数 check,二是"重新开始"的 restart。先看 check:

let secret = Math.floor(Math.random() * 100) + 1;
let attempts = 0;

const input = document.getElementById('guess');
const feedback = document.getElementById('feedback');
const count = document.getElementById('count');

function check() {
  const value = Number(input.value);
  if (!Number.isInteger(value) || value < 1 || value > 100) {
    feedback.textContent = '请输入 1~100 之间的整数!';
    return;
  }
  attempts = attempts + 1;
  count.textContent = '已猜次数:' + attempts;
  if (value === secret) {
    feedback.textContent = '🎉 恭喜!你用了 ' + attempts + ' 次猜中!';
    input.disabled = true;
  } else if (value < secret) {
    feedback.textContent = '太小了,再大一点!';
  } else {
    feedback.textContent = '太大了,再小一点!';
  }
  input.value = '';
  input.focus();
}

逐行拆解:Math.random() 返回 0(含)到 1(不含)的小数,乘 100 后用 Math.floor 取整得到 0~99,再加 1 就得到 1~100 的整数,这是生成随机整数范围的标准写法;input.value 永远是字符串,必须用 Number() 转成数字;Number.isInteger 顺手过滤掉 3.5 这类小数。判断合法后累加次数、更新显示,最后根据"等于、小于、大于"三种情况给出反馈,并把输入框清空、重新聚焦,方便玩家连续输入。

关于"字符串比较"的坑,值得单独演示一下。打开 Console 跑下面这段,你会看到反直觉的结果:

// 错误示范:直接用字符串比较
console.log('10' < '9');        // true!因为按字典序,'1' 排在 '9' 前面
// 正确做法:先转成数字再比
console.log(Number('10') < Number('9'));  // false,10 当然比 9 大

字符串比较是一位一位按字符顺序来的,数字比较是按数值大小来的,两套完全不同的规则。这就是为什么读输入框的值之后,第一步永远是 Number()——这个坑几乎每个新手都踩过,踩一次记住一辈子。

猜中后设置 input.disabled = true 锁死输入框,防止玩家继续乱猜——这是"游戏结束"状态的最简单表达。接下来是事件绑定和重置逻辑:

function restart() {
  secret = Math.floor(Math.random() * 100) + 1;
  attempts = 0;
  input.disabled = false;
  input.value = '';
  feedback.textContent = '新的一局,开始猜吧!';
  count.textContent = '已猜次数:0';
  input.focus();
}

document.getElementById('submit').addEventListener('click', check);
input.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') check();
});
document.getElementById('restart').addEventListener('click', restart);

addEventListener 是给元素"挂监听"的标准方式:第一个参数是事件名,第二个参数是触发时要执行的函数。keydown 监听让玩家按回车也能提交,比只点按钮顺手得多。restart 的本质是把所有状态变量复位到初始值——注意 secret 也要重新随机,否则每局答案都一样就没意思了。

还有一个新手常踩的坑:监听器一定要在函数外面注册一次,千万别写进 check 里,否则每猜一次就多绑一个监听器,按钮会越点越"灵异"。

5. 完整代码

把前面三节的代码拼到一起,就是一个完整的 index.html。骨架如下,把对应部分的代码贴进注释标注的位置即可:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>猜数字</title>
  <style>
    /* 第 3 节的 CSS 原样贴到这里 */
  </style>
</head>
<body>
  <!-- 第 2 节的 HTML 原样贴到这里 -->
  <script>
    // 第 4 节的两段 JS 原样贴到这里
  </script>
</body>
</html>

保存后在浏览器打开:输入 50,根据提示判断往大还是往小,用"二分法"最多 7 次就能猜中。注意 <script> 要放在 <body> 内部靠后的位置——脚本执行时页面元素必须已经存在,否则 getElementById 会拿到 null。如果发现点按钮没反应,先按 F12 打开开发者工具看 Console 有没有报错,99% 的新手问题都逃不过这一招。

6. 总结与练习

这篇我们完成了第一个完整小游戏,核心知识点有三个:状态管理(答案和次数存在变量里,而不是散落在页面上)、DOM 读写(用 getElementById 拿元素、用 textContent 改文字)、事件驱动(点击和按键触发函数)。这三样,是后面所有网页游戏的通用骨架。

来几道练习题巩固一下:

下一篇我们做井字棋,你会发现"数据驱动界面"的思想会让更复杂的规则也变得清晰——猜数字只是热身,好戏还在后面。

💡 调试彩蛋:在 Console 里敲一句 secret 就能看到正确答案——不是让你作弊,而是让你快速验证"猜中"分支的代码是否正确。