Day 21: 项目3-猜数字游戏
🎯 Day 21 – 猜数字游戏
📂 分类: 09-项目实战
🔥 难度: ⭐⭐⭐☆☆
🎯 学习目标
- ✅ 掌握 Math.random() 和随机数生成
- ✅ 学会表单验证和用户输入处理
- ✅ 理解游戏循环和状态管理
- ✅ 掌握事件监听和交互逻辑
- ✅ 学会使用 CSS 动画增强体验
💡 项目概述
项目简介
猜数字游戏是学习交互式编程的经典项目,它包含:
- 🎲 随机数生成:Math.random() 的实际应用
- 🎮 游戏循环:开始 → 猜测 → 反馈 → 继续
- 🏆 胜负判定:比较猜测与目标数字
- 📊 统计追踪:记录猜测次数和最佳成绩
- 🎨 UI 反馈:视觉提示和动画效果
核心功能
- [ ] 生成 1-100 的随机数
- [ ] 接受用户输入并验证
- [ ] 判断大小并给出提示
- [ ] 记录猜测次数
- [ ] 重新开始游戏
- [ ] 历史最佳成绩
技术重点
- 随机数: Math.random()、Math.floor()
- DOM 操作: 表单、按钮、文本
- 事件处理: submit、click、keypress
- 状态管理: 游戏状态、历史记录
- CSS 动画: 过渡效果、视觉反馈
📝 项目结构
guess-game/
├── index.html # 主页面
├── style.css # 样式文件
└── script.js # JavaScript逻辑
🎮 完整代码
1. HTML (index.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猜数字游戏</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header>
<h1>🎲 猜数字游戏</h1>
<p class="subtitle">我有一个 1-100 之间的数字,你能猜到吗?</p>
</header>
<!-- 游戏信息 -->
<div class="game-info">
<div class="info-item">
<span class="label">尝试次数</span>
<span class="value" id="guessCount">0</span>
</div>
<div class="info-item">
<span class="label">历史最佳</span>
<span class="value" id="bestScore">-</span>
</div>
</div>
<!-- 游戏区域 -->
<div class="game-area">
<!-- 输入区域 -->
<div class="input-section">
<label for="guessInput">输入你的猜测 (1-100)</label>
<div class="input-group">
<input
type="number"
id="guessInput"
min="1"
max="100"
placeholder="输入数字..."
autocomplete="off"
>
<button id="submitBtn" class="btn btn-primary">猜!</button>
</div>
<p class="hint" id="inputHint"></p>
</div>
<!-- 反馈区域 -->
<div class="feedback-section" id="feedback">
<div class="feedback-icon" id="feedbackIcon">❓</div>
<div class="feedback-message" id="feedbackMessage">
开始游戏吧!输入你的猜测
</div>
<div class="feedback-detail" id="feedbackDetail"></div>
</div>
<!-- 历史记录 -->
<div class="history-section">
<h3>猜测历史</h3>
<div class="history-list" id="historyList">
<p class="empty">还没有猜测记录</p>
</div>
</div>
<!-- 重新开始 -->
<button id="restartBtn" class="btn btn-secondary" style="display: none;">
🔄 重新开始
</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
2. CSS (style.css)
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
/* 容器 */
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 500px;
width: 100%;
padding: 30px;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 头部 */
header {
text-align: center;
margin-bottom: 30px;
}
header h1 {
color: #333;
font-size: 32px;
margin-bottom: 10px;
}
.subtitle {
color: #666;
font-size: 14px;
}
/* 游戏信息 */
.game-info {
display: flex;
justify-content: space-around;
background: #f7f7f7;
border-radius: 12px;
padding: 15px;
margin-bottom: 30px;
}
.info-item {
text-align: center;
}
.info-item .label {
display: block;
color: #666;
font-size: 12px;
margin-bottom: 5px;
}
.info-item .value {
display: block;
color: #667eea;
font-size: 24px;
font-weight: bold;
}
/* 游戏区域 */
.game-area {
margin-top: 20px;
}
/* 输入区域 */
.input-section {
margin-bottom: 20px;
}
.input-section label {
display: block;
color: #333;
font-weight: 600;
margin-bottom: 10px;
}
.input-group {
display: flex;
gap: 10px;
}
#guessInput {
flex: 1;
padding: 15px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 18px;
text-align: center;
transition: border-color 0.3s;
}
#guessInput:focus {
outline: none;
border-color: #667eea;
}
.btn {
padding: 15px 30px;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
transform: scale(1.05);
}
.btn-secondary {
background: #f7f7f7;
color: #333;
width: 100%;
margin-top: 20px;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.hint {
color: #999;
font-size: 12px;
margin-top: 8px;
min-height: 18px;
}
.hint.error {
color: #f56565;
}
.hint.success {
color: #48bb78;
}
/* 反馈区域 */
.feedback-section {
background: #f7f7f7;
border-radius: 12px;
padding: 20px;
text-align: center;
margin-bottom: 20px;
transition: all 0.3s;
}
.feedback-section.too-high {
background: #fed7d7;
border: 2px solid #fc8181;
}
.feedback-section.too-low {
background: #bee3f8;
border: 2px solid #63b3ed;
}
.feedback-section.correct {
background: #c6f6d5;
border: 2px solid #48bb78;
animation: celebrate 0.5s ease;
}
@keyframes celebrate {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.feedback-icon {
font-size: 48px;
margin-bottom: 10px;
}
.feedback-message {
font-size: 20px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.feedback-detail {
font-size: 14px;
color: #666;
}
/* 历史记录 */
.history-section {
margin-bottom: 20px;
}
.history-section h3 {
font-size: 16px;
color: #333;
margin-bottom: 10px;
}
.history-list {
background: #f7f7f7;
border-radius: 10px;
padding: 15px;
max-height: 200px;
overflow-y: auto;
}
.history-list .empty {
color: #999;
text-align: center;
font-size: 14px;
}
.history-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-bottom: 1px solid #e0e0e0;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.history-item:last-child {
border-bottom: none;
}
.history-number {
font-weight: bold;
color: #333;
}
.history-arrow {
font-size: 12px;
margin: 0 10px;
}
.history-arrow.high {
color: #fc8181;
}
.history-arrow.low {
color: #63b3ed;
}
/* 响应式 */
@media (max-width: 480px) {
.container {
padding: 20px;
}
header h1 {
font-size: 24px;
}
.input-group {
flex-direction: column;
}
.btn-primary {
width: 100%;
}
}
3. JavaScript (script.js)
// ========== 游戏状态 ==========
class GuessGame {
constructor() {
this.targetNumber = this.generateRandomNumber();
this.guessCount = 0;
this.guessHistory = [];
this.gameOver = false;
this.bestScore = this.loadBestScore();
this.initializeElements();
this.attachEventListeners();
this.updateDisplay();
this.loadBestScore();
}
// 初始化 DOM 元素
initializeElements() {
this.elements = {
guessInput: document.getElementById('guessInput'),
submitBtn: document.getElementById('submitBtn'),
restartBtn: document.getElementById('restartBtn'),
guessCount: document.getElementById('guessCount'),
bestScore: document.getElementById('bestScore'),
feedback: document.getElementById('feedback'),
feedbackIcon: document.getElementById('feedbackIcon'),
feedbackMessage: document.getElementById('feedbackMessage'),
feedbackDetail: document.getElementById('feedbackDetail'),
historyList: document.getElementById('historyList'),
inputHint: document.getElementById('inputHint')
};
}
// 附加事件监听器
attachEventListeners() {
// 提交按钮
this.elements.submitBtn.addEventListener('click', () => {
this.handleGuess();
});
// 回车键提交
this.elements.guessInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.handleGuess();
}
});
// 输入验证
this.elements.guessInput.addEventListener('input', () => {
this.validateInput();
});
// 重新开始
this.elements.restartBtn.addEventListener('click', () => {
this.restart();
});
}
// 生成随机数 (1-100)
generateRandomNumber() {
return Math.floor(Math.random() * 100) + 1;
}
// 验证输入
validateInput() {
const value = this.elements.guessInput.value;
const hint = this.elements.inputHint;
if (value === '') {
hint.textContent = '';
hint.className = 'hint';
return false;
}
const num = parseInt(value);
if (isNaN(num)) {
hint.textContent = '请输入有效的数字';
hint.className = 'hint error';
return false;
}
if (num < 1 || num > 100) {
hint.textContent = '数字必须在 1-100 之间';
hint.className = 'hint error';
return false;
}
hint.textContent = '✓ 有效';
hint.className = 'hint success';
return true;
}
// 处理猜测
handleGuess() {
if (this.gameOver) {
return;
}
if (!this.validateInput()) {
this.shakeInput();
return;
}
const guess = parseInt(this.elements.guessInput.value);
this.guessCount++;
// 添加到历史
this.guessHistory.push(guess);
// 判断结果
if (guess === this.targetNumber) {
this.handleCorrectGuess(guess);
} else if (guess > this.targetNumber) {
this.handleTooHigh(guess);
} else {
this.handleTooLow(guess);
}
// 更新显示
this.updateDisplay();
this.addToHistory(guess, guess > this.targetNumber ? 'high' : 'low');
// 清空输入并聚焦
if (!this.gameOver) {
this.elements.guessInput.value = '';
this.elements.guessInput.focus();
}
}
// 猜对了
handleCorrectGuess(guess) {
this.gameOver = true;
// 更新最佳成绩
if (this.bestScore === null || this.guessCount < this.bestScore) {
this.bestScore = this.guessCount;
this.saveBestScore();
}
// 显示成功反馈
this.elements.feedback.className = 'feedback-section correct';
this.elements.feedbackIcon.textContent = '🎉';
this.elements.feedbackMessage.textContent = '恭喜你,猜对了!';
this.elements.feedbackDetail.textContent =
`数字是 ${this.targetNumber},你用了 ${this.guessCount} 次猜测`;
// 显示重新开始按钮
this.elements.restartBtn.style.display = 'block';
this.elements.submitBtn.disabled = true;
this.elements.guessInput.disabled = true;
}
// 猜大了
handleTooHigh(guess) {
this.elements.feedback.className = 'feedback-section too-high';
this.elements.feedbackIcon.textContent = '📈';
this.elements.feedbackMessage.textContent = '太大了!';
this.elements.feedbackDetail.textContent =
`你的猜测 ${guess} 比目标数字大`;
}
// 猜小了
handleTooLow(guess) {
this.elements.feedback.className = 'feedback-section too-low';
this.elements.feedbackIcon.textContent = '📉';
this.elements.feedbackMessage.textContent = '太小了!';
this.elements.feedbackDetail.textContent =
`你的猜测 ${guess} 比目标数字小`;
}
// 添加到历史记录
addToHistory(guess, direction) {
const historyItem = document.createElement('div');
historyItem.className = 'history-item';
const arrow = direction === 'high' ? '↓' : '↑';
const arrowClass = direction === 'high' ? 'high' : 'low';
historyItem.innerHTML = `
<span class="history-number">#${this.guessCount}: ${guess}</span>
<span class="history-arrow ${arrowClass}">${arrow}</span>
`;
// 移除空提示
const empty = this.elements.historyList.querySelector('.empty');
if (empty) {
empty.remove();
}
// 添加到开头
this.elements.historyList.insertBefore(
historyItem,
this.elements.historyList.firstChild
);
}
// 更新显示
updateDisplay() {
this.elements.guessCount.textContent = this.guessCount;
this.elements.bestScore.textContent =
this.bestScore !== null ? this.bestScore : '-';
}
// 输入框抖动效果
shakeInput() {
const input = this.elements.guessInput;
input.style.animation = 'none';
input.offsetHeight; // 触发重绘
input.style.animation = 'shake 0.5s ease';
}
// 重新开始
restart() {
this.targetNumber = this.generateRandomNumber();
this.guessCount = 0;
this.guessHistory = [];
this.gameOver = false;
// 重置 UI
this.elements.feedback.className = 'feedback-section';
this.elements.feedbackIcon.textContent = '❓';
this.elements.feedbackMessage.textContent = '开始游戏吧!输入你的猜测';
this.elements.feedbackDetail.textContent = '';
this.elements.restartBtn.style.display = 'none';
this.elements.submitBtn.disabled = false;
this.elements.guessInput.disabled = false;
this.elements.guessInput.value = '';
this.elements.guessInput.focus();
this.elements.historyList.innerHTML = '<p class="empty">还没有猜测记录</p>';
this.updateDisplay();
}
// 加载最佳成绩
loadBestScore() {
const saved = localStorage.getItem('guessGameBestScore');
if (saved) {
this.bestScore = parseInt(saved);
}
}
// 保存最佳成绩
saveBestScore() {
localStorage.setItem('guessGameBestScore', this.bestScore);
}
}
// 添加抖动动画
const style = document.createElement('style');
style.textContent = `
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
`;
document.head.appendChild(style);
// 初始化游戏
document.addEventListener('DOMContentLoaded', () => {
const game = new GuessGame();
console.log('🎮 猜数字游戏已启动!');
console.log('💡 提示:按回车键快速提交猜测');
});
🎯 项目亮点
1. 完整的游戏循环
开始 → 生成随机数 → 接受输入 → 判断 → 反馈 → 继续/结束
2. 状态管理
class GuessGame {
constructor() {
this.targetNumber = null; // 目标数字
this.guessCount = 0; // 猜测次数
this.guessHistory = []; // 历史记录
this.gameOver = false; // 游戏状态
this.bestScore = null; // 最佳成绩
}
}
3. 用户输入验证
validateInput() {
// 检查是否为数字
// 检查范围是否有效
// 提供实时反馈
}
4. 本地存储最佳成绩
// 保存到 localStorage
localStorage.setItem('guessGameBestScore', this.bestScore);
// 从 localStorage 加载
const saved = localStorage.getItem('guessGameBestScore');
✍️ 练习任务
练习1: 基础功能(30分钟)
- [ ] 实现随机数生成 (1-100)
- [ ] 实现输入验证
- [ ] 实现大小比较逻辑
- [ ] 显示正确/错误反馈
- [ ] 测试基本功能
练习2: 进阶功能(45分钟)
- [ ] 添加猜测次数统计
- [ ] 实现历史记录显示
- [ ] 添加最佳成绩保存
- [ ] 实现重新开始功能
- [ ] 添加输入动画效果
练习3: UI 优化(30分钟)
- [ ] 添加欢迎界面
- [ ] 实现猜测动画
- [ ] 优化移动端布局
- [ ] 添加音效提示
- [ ] 美化反馈样式
练习4: 功能扩展(60分钟)
- [ ] 添加难度选择(简单/困难)
- [ ] 实现计时器功能
- [ ] 添加提示功能(显示范围)
- [ ] 实现多人模式
- [ ] 添加成就系统
💡 常见问题 FAQ
Q1: Math.random() 真的随机吗?
A: 不是真随机,是伪随机。但对于游戏来说足够了。
// 生成 1-100 的随机整数
Math.floor(Math.random() * 100) + 1;
Q2: 如何防止用户输入无效数字?
A: 使用 HTML5 验证 + JavaScript 验证。
<input type="number" min="1" max="100">
if (num < 1 || num > 100) {
// 显示错误
}
Q3: 如何让游戏更有趣?
A: 可以添加:
- 倒计时模式
- 有限次猜测
- 提示功能
- 难度级别
Q4: 最佳成绩如何持久化?
A: 使用 localStorage。
// 保存
localStorage.setItem('bestScore', score);
// 读取
const score = localStorage.getItem('bestScore');
📚 拓展学习
推荐项目
- 石头剪刀布:理解概率和选择
- 井字棋:学习 AI 和游戏逻辑
- 记忆翻牌:掌握状态管理
相关主题
- 数组方法(push、filter、map)
- 对象和类
- DOM 操作
- 事件处理
🎓 总结
今天我们学到了:
- ✅ Math.random() 生成随机数
- ✅ 表单验证和用户输入处理
- ✅ 游戏状态管理
- ✅ localStorage 数据持久化
- ✅ 完整的游戏开发流程
关键概念:
- 🎲 随机数:Math.random() + Math.floor()
- 🎮 游戏循环:输入 → 处理 → 反馈
- 💾 状态管理:跟踪游戏数据
- 🎨 UI 反馈:视觉和动画效果
学习时间: 2026-03-17
难度: ⭐⭐⭐☆☆
预计用时: 3-4 小时
关键词: 猜数字、随机数、游戏开发、DOM操作
相关标签: #09-项目实战
📌 下一步: 学习 Day 22: 项目4-打字练习游戏,继续游戏开发之旅!