// ============================================================
// 零基础 Rust 图形入门:用控制台“画”出会动的小球
// 仅使用标准库,不需要任何第三方依赖
// 运行方式:cargo run
// 请在现代终端中运行(Windows Terminal、macOS 终端、Linux 终端均支持)
// ============================================================
use std::thread::sleep;
use std::time::Duration;
fn main() {
// ---------- 1. 定义画布大小 ----------
// 这就是我们“屏幕”的尺寸,用字符来模拟像素
const WIDTH: usize = 40; // 一行有多少个字符(水平方向像素数)
const HEIGHT: usize = 20; // 一共有多少行(垂直方向像素数)
// ---------- 2. 创建像素缓冲区 ----------
// 二维数组 canvas[y][x] 存放每个“像素”的字符
// 初始全部用 '.' 表示空白背景
let mut canvas = vec![vec!['.'; WIDTH]; HEIGHT];
// ---------- 3. 小球的状态 ----------
let ball_char = '@'; // 用 @ 代表小球
let mut ball_x = WIDTH / 2; // 小球初始水平位置(列)
let mut ball_y = HEIGHT / 2;// 小球初始垂直位置(行)
let mut dx: i32 = 1; // 水平方向速度(向右为正)
let mut dy: i32 = 1; // 垂直方向速度(向下为正)
// ---------- 4. 动画主循环 ----------
// 这个无限循环就是“游戏主循环”,每一轮循环就是一帧画面
loop {
// 4.1 清空画布(全部重新涂上背景色)
for row in canvas.iter_mut() {
row.fill('.'); // 把整行所有元素设为 '.'
}
// 4.2 在画布上画出小球(修改像素缓冲区)
// 注意边界检查:小球位置不能超出画布
if ball_y < HEIGHT && ball_x < WIDTH {
canvas[ball_y][ball_x] = ball_char;
}
// 4.3 更新小球位置(下一帧移动多少)
// 用 i32 做运算,后面会转回 usize
let new_x = ball_x as i32 + dx;
let new_y = ball_y as i32 + dy;
// 碰到左右边界就反弹(反转水平速度)
if new_x <= 0 || new_x >= (WIDTH - 1) as i32 {
dx = -dx;
}
// 碰到上下边界就反弹(反转垂直速度)
if new_y <= 0 || new_y >= (HEIGHT - 1) as i32 {
dy = -dy;
}
// 应用新位置(转为 usize 存储)
ball_x = (ball_x as i32 + dx) as usize;
ball_y = (ball_y as i32 + dy) as usize;
// ---------- 5. 把画布“打印”到控制台 ----------
// 5.1 先移动光标到左上角,然后清除屏幕
// \x1B[2J 是 ANSI 转义序列,表示清除整个屏幕
// \x1B[1;1H 表示把光标放到第1行第1列
print!("\x1B[2J\x1B[1;1H");
// 5.2 逐行打印画布
for row in &canvas {
// 把一行的字符收集成字符串
let line: String = row.iter().collect();
println!("{}", line);
}
// 5.3 强制刷新输出(有些终端需要手动刷新才能即时显示)
use std::io::{stdout, Write};
stdout().flush().unwrap();
// 5.4 暂停一小段时间,控制动画速度
// 大约每秒 10 帧(100 毫秒一帧)
sleep(Duration::from_millis(100));
}
}