1 use std::{ io::{Write, stdout}, time::Duration}; 2 3 use crossterm::{QueueableCommand, Result, cursor::*, event::{*, self}, execute, style::*, terminal::{*, self}}; 4 use rand::Rng; 5 6 fn main() -> Result<()> { 7 let (columns, rows) = terminal::size()?; 8 9 execute!(stdout(), 10 EnterAlternateScreen, // 进入替代屏幕 11 // SetSize(MAP_ROW as u16 * 2, MAP_COL as u16 *2 + 20), // 设置窗口大小 12 SetSize(WIDTH , HEIGHT), // 设置窗口大小 13 SetTitle("贪吃蛇"), // 标题 14 DisableLineWrap, // 禁止自动换行 15 Hide, // 影藏光标 16 )?; 17 println!("按键操作: 'Esc/Q' - 退出, '↑/↓/←/→' - 移动"); 18 // println!("{}", "◇◆●○■□██◁▶▉▉██╳╳><╱╲██♪♩▓▉88"); 19 enable_raw_mode()?; // 进入原始模式, 禁止 CTL+C 等特殊键, println!不能用等等 20 21 let mut game = SnakeGame::new(); 22 game.draw()?; 23 loop { 24 // poll在指定时间内尝试获取事件, 一旦获取事件就返回 25 // 所以一旦持续操作能加快速度, 这个可以通过睡眠(间隔时间 - 消耗时间)解决 26 // 这里不做处理 27 if event::poll(Duration::from_millis(150))? { 28 let event = event::read()?; 29 30 if let Event::Key(KeyEvent{code, modifiers: _}) = event { 31 let running = if let GameStatus::Running = game.status { true } else { false }; 32 match code { 33 KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => break, // quit 34 KeyCode::Char('r') | KeyCode::Char('R') => // restart 35 if let GameStatus::Over = game.status { 36 game = SnakeGame::new(); 37 }, 38 // 刚开始不能向左 face(0, 0), 所有Left多一个判断 39 KeyCode::Left if running && game.face.1 == 0 && game.face.0 != 0 => game.face = (0, -1), 40 KeyCode::Right if running && game.face.1 == 0 => game.face = (0, 1), 41 KeyCode::Up if running && game.face.0 == 0 => game.face = (-1, 0), 42 KeyCode::Down if running && game.face.0 == 0 => game.face = (1, 0), 43 _ => (), 44 } 45 } 46 } 47 48 if let GameStatus::Running = game.status { 49 if !game.moving() { // game over 50 game.over(); 51 } } 52 53 game.draw()?; 54 } 55 disable_raw_mode()?; 56 // 还原窗口 57 execute!(stdout(), LeaveAlternateScreen, DisableLineWrap, SetSize(columns, rows)) 58 } 59 60 61 const MAP_ROW: usize = 23; 62 const MAP_COL: usize = 23; 63 64 const OFFSET_ROW: u16 = 4; 65 const OFFSET_COL: u16 = OFFSET_ROW * 2; 66 const WIDTH: u16 = (MAP_COL as u16 + OFFSET_COL) * 2; 67 const HEIGHT: u16 = MAP_ROW as u16 + OFFSET_ROW * 2; 68 69 70 struct SnakeGame { 71 map: [[CellValue; MAP_COL]; MAP_ROW], 72 snake: Vec<(usize, usize, SnakeValue)>, 73 face: (i32, i32), 74 count: usize, 75 status: GameStatus, 76 } 77 78 enum GameStatus { 79 Running, 80 Over, 81 } 82 83 #[derive(Clone, Copy)] 84 enum CellValue { 85 Empty, // 空 86 Food, // 食物 87 Snake(SnakeValue), // 88 } 89 90 #[derive(Clone, Copy)] 91 enum SnakeValue { 92 Head, 93 Trunk, 94 Grow, 95 Tail, 96 } 97 98 impl SnakeGame { 99 100 fn new() -> Self { 101 let mut map = [[CellValue::Empty; MAP_COL]; MAP_ROW]; 102 // new food 103 let mut rng = rand::thread_rng(); 104 let (food_row, food_col) = (rng.gen_range(0..MAP_ROW), rng.gen_range(6..MAP_COL)); 105 map[food_row][food_col] = CellValue::Food; 106 let snake = Self::new_snake(food_row, food_col - 2); 107 for &(r, c, sv) in snake.iter() { 108 map[r][c] = CellValue::Snake(sv); 109 } 110 111 Self { 112 map, 113 snake, 114 face: (0, 0), 115 count: 0, 116 status: GameStatus::Running 117 } 118 } 119 120 // 初始化, 和食物保持同一行并在左边2格外 121 fn new_snake(row: usize, col: usize) -> Vec<(usize, usize, SnakeValue)> { 122 let mut rng = rand::thread_rng(); 123 let (row, col) = (row, rng.gen_range(3..col)); 124 let snake = vec![ 125 (row, col, SnakeValue::Head), 126 (row, col - 1, SnakeValue::Trunk), 127 (row, col - 2, SnakeValue::Tail), 128 ]; 129 snake 130 } 131 132 fn rnd_food(&mut self) { 133 let mut rng = rand::thread_rng(); 134 loop { 135 let (row, col) = (rng.gen_range(0..MAP_ROW), rng.gen_range(0..MAP_COL)); 136 if let CellValue::Empty = self.map[row][col] { 137 self.map[row][col] = CellValue::Food; 138 break; 139 } 140 } 141 } 142 143 fn over(&mut self){ 144 self.status = GameStatus::Over; 145 } 146 147 fn draw(&mut self) -> Result<()> { 148 let mut out = stdout(); 149 // 绘制地图 150 let mut background = Color::Cyan; 151 execute!(out, MoveTo(OFFSET_COL, OFFSET_ROW))?; 152 for rows in &self.map { 153 for value in rows { 154 background = if let Color::Cyan = background { Color::DarkCyan } else { Color::Cyan }; 155 let (cell, color) = match value { 156 CellValue::Empty => (" ", Color::DarkRed), 157 CellValue::Food => ("◆", Color::DarkRed) , 158 CellValue::Snake(SnakeValue::Head) => ("◆", Color::DarkMagenta) , 159 CellValue::Snake(SnakeValue::Trunk) => ("●", Color::DarkMagenta) , 160 CellValue::Snake(SnakeValue::Grow) => ("■", Color::DarkMagenta) , 161 CellValue::Snake(SnakeValue::Tail) => ("○", Color::DarkMagenta) , 162 }; 163 execute!(out, 164 SetBackgroundColor(background), 165 SetForegroundColor(color), 166 // Print text 167 Print(cell.to_string()), 168 // Reset to default colors 169 ResetColor 170 )?; 171 } 172 execute!(out, MoveToNextLine(1), MoveToColumn(OFFSET_COL + 1))?; 173 } 174 // 记录 175 out .queue(MoveToNextLine(1))? 176 .queue(MoveToColumn(OFFSET_COL + 1))? 177 .queue(PrintStyledContent(format!("eat: {:4}", self.count).red()))? 178 ; 179 // game over 180 execute!(out, MoveTo(0, 1))?; 181 let info = if let GameStatus::Over = self.status { 182 execute!( out, 183 SetBackgroundColor(Color::DarkRed), 184 SetForegroundColor(Color::White))?; 185 "Game Over! (Press key 'R' Restart)" 186 187 }else { 188 " " 189 }; 190 execute!( out, 191 // Print text 192 Print(format!("{:^width$}", " ", width=WIDTH as usize)), 193 MoveToNextLine(1), 194 Print(format!("{:^width$}", info, width=WIDTH as usize)), 195 MoveToNextLine(1), 196 Print(format!("{:^width$}", " ", width=WIDTH as usize)), 197 // Reset to default colors 198 ResetColor 199 )?; 200 out.flush() 201 } 202 203 204 fn moving(&mut self) -> bool { 205 if self.face.0 == 0 && self.face.1 == 0 { return true; } // 静止不动 206 // 计算蛇头下一步 207 let (forward_row, forward_col) = (self.snake[0].0 as i32 + self.face.0, self.snake[0].1 as i32 + self.face.1); 208 // 撞墙 209 if forward_row < 0 || forward_col < 0 { return false } 210 let mut forward = (forward_row as usize, forward_col as usize, SnakeValue::Head); 211 // 还是撞墙, 类型一致才能比较 212 if forward.0 == MAP_ROW || forward.1 == MAP_COL { return false } 213 // 判定下一步 214 match self.map[forward.0][forward.1] { 215 CellValue::Empty => { // 移动, 所有格子向前移一步(交换) 216 for ele in self.snake.iter_mut() { 217 let temp = *ele; 218 let mut v = temp.2; 219 if let SnakeValue::Trunk = v { 220 if let SnakeValue::Grow = forward.2 { 221 v = SnakeValue::Grow; 222 } 223 }else if let SnakeValue::Grow = v { 224 v = SnakeValue::Trunk; 225 } 226 *ele = (forward.0, forward.1, v); 227 // 设置不能归纳为一个方法, for循环内 &mut self 不能同时存在多个 228 self.map[forward.0][forward.1] = CellValue::Snake(v); 229 forward = temp; 230 } 231 // 最后一格清空 232 self.map[forward.0][forward.1] = CellValue::Empty; 233 true 234 }, 235 CellValue::Food => { // 食物, 头向前移, 原头增加一格 236 self.count += 1; 237 self.snake.insert(0, (forward.0, forward.1, SnakeValue::Head)); 238 self.snake[1].2 = SnakeValue::Grow; 239 self.map[forward.0][forward.1] = CellValue::Snake(forward.2); 240 self.map[self.snake[1].0][self.snake[1].1] = CellValue::Snake(self.snake[1].2); 241 self.rnd_food(); 242 true 243 } , 244 _=> false // 撞上蛇躯 245 } 246 } 247 }
Cargo.toml
rand = "0.8.4"
crossterm = "0.22.1"