一、游戏简介

这是一款基于 Rust 语言开发的控制台投篮小游戏。玩家将扮演篮球运动员,通过输入角度和力度控制控制投篮,挑战在限定时间内投中更多球。游戏核心机制包括物理引擎模拟、随机风速干扰、计分系统和难度递增,所有逻辑封装在单一结构体中,适合 Rust 初学者学习控制台交互与物理模拟基础。

二、核心功能

五、玩法攻略

六、Rust 特性应用解析

七、扩展方向

这款投篮小游戏不仅是对 Rust 基础语法的实践,更融合了物理模拟、用户交互和游戏设计思想。通过调整物理参数(如重力、空气阻力),可以模拟不同环境下的投篮体验(例如「月球模式」低重力),为游戏扩展提供了广阔空间。对于 Rust 初学者而言,这是一个理解结构体设计、数值计算和控制台交互的绝佳

  1. 物理模拟:基于抛射运动公式计算篮球轨迹,考虑角度、初速度和风速影响
  2. 随机干扰:每轮随机生成风速,影响投篮精度
  3. 难度递增:随得分增加,投篮距离逐渐变远
  4. 计时系统:每轮有 10 秒决策时间,超时自动判定失败
  5. 状态展示:实时显示抛物线轨迹、剩余时间和历史成绩
    use std::collections::VecDeque;
    use std::io;
    use std::time::{Duration, Instant};
    use rand::Rng as _;
    use rand::thread_rng;
    
    // 游戏状态结构体(单类封装所有逻辑)
    struct BasketballGame {
        score: i32,               // 当前得分
        high_score: i32,          // 最高分
        distance: f64,            // 投篮距离(米)
        wind: f64,                // 风速(影响水平偏移,-2.0到2.0)
        history: VecDeque<String>,// 历史记录(最近5条)
        game_over: bool,          // 游戏是否结束
    }
    
    impl BasketballGame {
        // 初始化游戏
        fn new() -> Self {
            BasketballGame {
                score: 0,
                high_score: 0,
                distance: 5.0,       // 初始距离5米(NBA三分线约7.25米)
                wind: 0.0,
                history: VecDeque::with_capacity(5),
                game_over: false,
            }
        }
    
        // 显示游戏状态
        fn show_status(&self) {
            println!("\n===== 篮球投篮游戏 =====");
            println!("当前得分: {} | 最高分: {}", self.score, self.high_score);
            println!("投篮距离: {:.1}米 | 风速: {:.1}m/s (左负右正)", self.distance, self.wind);
            println!("-------------------------");
            println!("历史记录:");
            if self.history.is_empty() {
                println!("  暂无记录");
            } else {
                for record in &self.history {
                    println!("  {}", record);
                }
            }
            println!("=========================");
        }
    
        // 生成随机风速
        fn generate_wind(&mut self) {
            let mut rng = thread_rng();
            self.wind = rng.random_range(-2.0..=2.0); // 风速范围:-2到2m/s
        }
    
        // 计算投篮结果(物理模拟)
        fn calculate_result(&self, angle: f64, velocity: f64) -> (bool, f64) {
            // 角度转弧度
            let rad = angle.to_radians();
            // 分解速度分量
            let vx = velocity * rad.cos(); // 水平速度
            let vy = velocity * rad.sin(); // 垂直速度
            
            // 重力加速度(m/s²)
            const GRAVITY: f64 = 9.8;
            // 篮球直径(约0.24米,用于判定命中范围)
            const BALL_SIZE: f64 = 0.24;
            // 篮筐直径(约0.45米)
            const HOOP_SIZE: f64 = 0.45;
    
            // 飞行时间(垂直方向速度减为0的时间)
            let time_to_peak = vy / GRAVITY;
            // 最大高度
            let peak_height = vy * time_to_peak - 0.5 * GRAVITY * time_to_peak.powi(2);
    
            // 到达篮筐水平距离所需时间(考虑风速影响)
            // 风速会导致水平加速度变化(简化模型:wind * 0.1)
            let horizontal_accel = self.wind * 0.1;
            // 水平方向运动方程:distance = vx * t + 0.5 * horizontal_accel * t²
            // 解二次方程 a*t² + b*t + c = 0
            let a = 0.5 * horizontal_accel;
            let b = vx;
            let c = -self.distance;
            let discriminant = b.powi(2) - 4.0 * a * c;
    
            if discriminant < 0.0 {
                return (false, 1000.0); // 无法到达篮筐距离
            }
    
            let t1 = (-b + discriminant.sqrt()) / (2.0 * a);
            let t2 = (-b - discriminant.sqrt()) / (2.0 * a);
            let flight_time = if t1 > 0.0 { t1 } else { t2 };
    
            // 到达篮筐时的高度
            let height_at_hoop = vy * flight_time - 0.5 * GRAVITY * flight_time.powi(2);
            // 篮筐标准高度3.05米,允许±0.3米误差
            let height_ok = height_at_hoop >= 2.75 && height_at_hoop <= 3.35;
    
            // 水平偏移(风速导致)
            let horizontal_offset = 0.5 * horizontal_accel * flight_time.powi(2);
            // 总偏移量(需小于篮筐和篮球的半径和)
            let total_offset = horizontal_offset.abs();
            let offset_ok = total_offset <= (HOOP_SIZE + BALL_SIZE) / 2.0;
    
            // 命中判定
            let is_scored = height_ok && offset_ok;
            (is_scored, peak_height)
        }
    
        // 显示投篮轨迹(简化版)
        fn show_trajectory(&self, peak_height: f64) {
            println!("\n投篮轨迹模拟:");
            let max_rows = 10; // 轨迹显示行数
            let height_ratio = max_rows as f64 / peak_height.min(10.0); // 高度缩放比例
    
            for i in 0..=max_rows {
                let current_height = peak_height * (1.0 - (i as f64 / max_rows as f64 - 0.5).powi(2) * 4.0);
                let row_height = current_height * height_ratio;
                let stars = row_height.max(0.0).min(1.0) * 20.0; // 水平长度
    
                // 绘制轨迹线
                if stars > 0.0 {
                    let stars_str = "*".repeat(stars as usize);
                    println!("  {:^30}", stars_str); // 居中显示
                }
            }
            println!("  {:^30}", "篮筐"); // 篮筐位置标记
        }
    
        // 单轮游戏逻辑
        fn play_round(&mut self) {
            self.generate_wind();
            self.show_status();
    
            println!("\n第{}轮:请输入投篮参数", self.score + 1);
            println!("提示:角度建议15-60度,速度建议8-15m/s(风速会影响偏移)");
    
            // 计时开始
            let start_time = Instant::now();
            const TIME_LIMIT: u64 = 10; // 10秒限时
    
            // 获取角度输入
            let angle = loop {
                print!("请输入投篮角度(度): ");
                let mut input = String::new();
                io::stdin().read_line(&mut input).unwrap();
    
                // 检查超时
                if start_time.elapsed() > Duration::from_secs(TIME_LIMIT) {
                    println!("\n⏰ 超时!本轮失败");
                    self.game_over = true;
                    return;
                }
    
                match input.trim().parse::<f64>() {
                    Ok(num) if num > 0.0 && num < 90.0 => break num,
                    _ => println!("无效角度!请输入0-90之间的数字"),
                }
            };
    
            // 获取速度输入
            let velocity = loop {
                print!("请输入投篮速度(m/s): ");
                let mut input = String::new();
                io::stdin().read_line(&mut input).unwrap();
    
                // 检查超时
                if start_time.elapsed() > Duration::from_secs(TIME_LIMIT) {
                    println!("\n⏰ 超时!本轮失败");
                    self.game_over = true;
                    return;
                }
    
                match input.trim().parse::<f64>() {
                    Ok(num) if num > 0.0 && num < 20.0 => break num,
                    _ => println!("无效速度!请输入0-20之间的数字"),
                }
            };
    
            // 计算结果
            let (scored, peak_height) = self.calculate_result(angle, velocity);
            self.show_trajectory(peak_height);
    
            // 处理结果
            if scored {
                self.score += 1;
                self.high_score = self.high_score.max(self.score);
                // 每得3分增加距离(难度提升)
                if self.score % 3 == 0 {
                    self.distance += 0.5;
                    self.distance = self.distance.min(8.0); // 最大距离8米
                }
                self.history.push_back(format!("命中!角度{:.1}° 速度{:.1}m/s", angle, velocity));
                println!("\n🎉 投中了!当前得分:{}", self.score);
            } else {
                self.history.push_back(format!("未中!角度{:.1}° 速度{:.1}m/s", angle, velocity));
                println!("\n❌ 未投中!游戏结束");
                self.game_over = true;
            }
    
            // 保持历史记录最多5条
            if self.history.len() > 5 {
                self.history.pop_front();
            }
        }
    
        // 运行游戏主循环
        fn run(&mut self) {
            println!("===== 欢迎来到控制台投篮游戏 =====");
            println!("游戏规则:");
            println!("1. 每轮需输入投篮角度(0-90度)和初速度(0-20m/s)");
            println!("2. 风速会影响投篮轨迹,左负右正");
            println!("3. 每轮有10秒决策时间,超时失败");
            println!("4. 投中得分,连续命中会增加距离提升难度");
            println!("5. 未投中则游戏结束,记录最高分");
            println!("----------------------------------");
            println!("按Enter开始游戏...");
            io::stdin().read_line(&mut String::new()).unwrap();
    
            while !self.game_over {
                self.play_round();
                if !self.game_over {
                    println!("\n按Enter继续下一轮...");
                    io::stdin().read_line(&mut String::new()).unwrap();
                }
            }
    
            println!("\n===== 游戏结束 =====");
            println!("最终得分:{} | 最高分:{}", self.score, self.high_score);
            println!("感谢游玩!");
        }
    }
    
    // 主函数
    fn main() {
        let mut game = BasketballGame::new();
        game.run();
    }
  6. 四、代码解析
  7. 结构体设计BasketballGame 结构体封装了所有游戏状态:得分、距离、风速、历史记录等核心数据,通过 impl 块实现所有游戏逻辑(物理计算、输入处理、状态展示等),严格遵循「单一类」设计要求。

  8. 物理模拟核心投篮轨迹计算基于经典抛射运动公式:

    • 垂直方向:受重力影响做匀减速运动(h = v_y * t - 0.5 * g * t²
    • 水平方向:受风速干扰(简化为水平加速度),通过二次方程求解飞行时间
    • 命中判定:同时满足高度在篮筐范围内(2.75-3.35 米)和水平偏移量小于篮筐与篮球的半径和
  9. 交互与节奏控制

    • 控制台输入采用循环验证模式,确保角度和速度在合理范围
    • 10 秒限时机制通过 Instant::now() 与 Duration 实现,超时自动判定失败
    • 历史记录使用 VecDeque 存储,保持最近 5 条记录,自动淘汰旧数据
  10. 难度递增系统初始投篮距离为 5 米(接近 NBA 三分线),每连续命中 3 次增加 0.5 米距离(最大 8 米),迫使玩家不断调整投篮参数,提升游戏挑战性。

  11. 基础参数参考

    • 5 米距离:推荐角度 30-40 度,速度 9-11m/s
    • 7 米距离:推荐角度 40-50 度,速度 11-13m/s
    • 逆风(负风速):适当增加角度补偿水平偏移
    • 顺风(正风速):适当减小角度避免偏移过量
  12. 进阶技巧

    • 观察风速:风速每轮随机生成,正数向右偏移,负数向左偏移,需针对性调整角度
    • 控制节奏:每轮有 10 秒时间,建议先观察风速再决定参数,避免仓促输入
    • 距离适应:距离增加时,不仅要提高速度,还需增大角度以保证足够的飞行高度
  13. 常见问题

    • 总是短距离:速度不足或角度太小,导致篮球未到达篮筐就落地
    • 总是过远:速度过大或角度太大,篮球飞过篮筐
    • 高度不够:角度太小,导致篮球在到达篮筐时已下落过低
  14. 类型安全与数值验证输入解析时使用 match 语句严格验证数值范围(如角度 0-90 度),结合 if 守卫确保参数合法性,体现 Rust 对类型安全的严格要求。

  15. 结构体与方法封装游戏状态与行为通过结构体紧密绑定,例如 generate_wind 方法直接修改结构体的 wind 字段,play_round 方法统筹单轮游戏流程,符合面向对象的封装思想。

  16. 集合类型选择使用 VecDeque 存储历史记录,利用其高效的首尾操作特性(push_back 和 pop_front),确保只保留最近 5 条记录,兼顾性能与内存占用。

  17. 时间处理通过 std::time 模块实现精确计时,Instant::elapsed() 实时计算输入耗时,超时机制增强了游戏的紧张感。

  18. 物理计算中的浮点数处理使用 f64 类型进行高精度物理计算,通过 powi 方法处理幂运算,sqrt 方法求解二次方程,展示了 Rust 对科学计算的良好支持。

  19. 增加多种投篮模式:如三分球大赛(固定距离限时)、障碍赛(需避开随机障碍物)
  20. 实现球员属性系统:不同球员有不同的力量(影响速度上限)和精准度(影响风速抗性)
  21. 添加音效反馈:结合 rodio 库在命中 / 未中时播放对应音效
  22. 图形化升级:使用 tui-rs 绘制更直观的轨迹动画,或用 bevy 引擎开发 2D 版本
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐