rust贪吃蛇

 

 

 

  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"

 

原文地址:https://www.cnblogs.com/harvard/p/15544703.html