rust俄罗斯方块

  1 use std::{io::{Write, stdout}, thread, time::{Duration, SystemTime}};
  2 
  3 use crossterm::{QueueableCommand, Result, cursor::{self, MoveTo, MoveToNextLine, RestorePosition, SavePosition}, event::{Event, KeyCode, KeyEvent, poll, read}, execute, style::{Attribute, Color, PrintStyledContent, Stylize}, terminal::{ EnterAlternateScreen, LeaveAlternateScreen, SetSize, SetTitle, disable_raw_mode, enable_raw_mode, size}};
  4 use rand::Rng;
  5 
  6 
  7 // 方块形状定义 7种方块 4个方向, 采用4x4的方阵 二进制01存储
  8 const SHAPES: [[u16;4];7] = [
  9                                 [15,8738, 15,8738], // 长条
 10                                 [23,1570,116,1094], // L
 11                                 [71,550,113,1604], //
 12                                 [39,610,114,1124], //
 13                                 [54,1122, 54,1122], //
 14                                 [99,612, 99,612], //
 15                                 [102,102, 102,102] //
 16                             ];
 17 
 18 const LINES: usize = 23;
 19 const COLS: usize = 12;
 20 
 21 const VALUE_EMPTY: u8 = 0;
 22 const VALUE_FILL: u8 = 1;
 23 const VALUE_FIXED: u8 = 2;
 24 const VALUE_CLEAR: u8 = 3;
 25 
 26 struct Tetris {
 27     map: [[u8; COLS]; LINES],
 28     shapes: [[[bool; 16]; 4]; SHAPES.len()],
 29     cur_shape: (usize, usize),
 30     cur_position: (i32, i32),
 31     next_shape: (usize, usize),
 32     status: Status,
 33     remove: usize,
 34 }
 35 
 36 enum ShapeOpt {
 37     CLEAR,  // 清除方块
 38     FILL,   // 填充方块
 39     FIXED,  // 固定方块
 40     COLLISION(i32, i32), // 碰撞检测(line位移, cell位移)
 41 }
 42 
 43 impl Tetris {
 44     fn new() -> Tetris{
 45         // 地图
 46         let map = [[VALUE_EMPTY;COLS];LINES];
 47 
 48         // 图形展开
 49         let mut shapes = [[[false;16];4];SHAPES.len()];
 50         for (i, ele) in shapes.iter_mut().enumerate() {
 51             for (j, arr) in ele.iter_mut().enumerate() {
 52                 for (k, v) in arr.iter_mut().enumerate() {
 53                    *v = 1 << (15 - k) & SHAPES[i][j] != 0;
 54                 }
 55             }
 56         }
 57         Tetris { 
 58             map,  
 59             shapes, 
 60             cur_shape: Tetris::rnd(), 
 61             cur_position: Tetris::init_position(), 
 62             next_shape: Tetris::rnd(), 
 63             status: Status::GameRunning, 
 64             remove: 0}
 65     }
 66 
 67     // 速度
 68     fn speed(&self) -> f64 {
 69         5.0_f64.min(1.0 + 0.1 * (self.remove as f64))
 70     }
 71 
 72     // 绘图
 73     fn draw(&self) -> Result<()> {
 74         let mut stdout = stdout();
 75         stdout.queue(MoveTo(0, 0))?
 76               .queue(cursor::Hide)?
 77               .queue(PrintStyledContent("按'Esc / Q': 退出; 's': 暂停/启动/重启".red()))?
 78         ;
 79         // 绘制地图
 80         for line in &self.map {
 81             stdout.queue(MoveToNextLine(0))?;
 82             for cell in line {
 83                 stdout.queue(PrintStyledContent(
 84                     match *cell {
 85                         VALUE_FILL => "".green(),
 86                         VALUE_FIXED => "".grey(),
 87                         VALUE_CLEAR => "".red(),
 88                         _ => "".blue(),
 89                     }
 90                 ))?;
 91             }
 92         }
 93 
 94         // 绘制下一方块 
 95         let (nx, ny) = ((COLS * 2 + 4) as u16, ((LINES - 10)/ 2)  as u16);
 96         stdout.queue(MoveToNextLine(0))?
 97               .queue(SavePosition)?
 98               .queue(MoveTo(nx, ny))?
 99               .queue(PrintStyledContent("下一形状: ".green()))?
100               .queue(MoveTo(nx + 1, ny + 1))?
101         ;
102         let next = &self.shapes[self.next_shape.0][self.next_shape.1];
103         for (i, block) in next.iter().enumerate() {
104 
105             if i % 4 == 0 {
106                 stdout.queue(MoveTo(nx + 1, ny + 1 + i as u16 / 4))?;
107             }
108             stdout.queue(PrintStyledContent( 
109                 if *block {
110                     if let Status::GameOver = self.status {
111                         "".grey() 
112                     } else {
113                         "".green() 
114                     }
115                 }else {
116                     "".blue()
117                 }
118             ))?;
119         }
120 
121         stdout
122         .queue(MoveTo(nx, ny + 5))?
123         .queue(PrintStyledContent(format!("Remove: {}", self.remove).red()))?
124         .queue(MoveTo(nx, ny + 6))?
125         .queue(PrintStyledContent(format!("Speed: {:.1}", self.speed()).green()))?
126         ;
127 
128 
129         if let Status::GameOver = self.status {
130             stdout.queue(MoveToNextLine(0))?
131             .queue(MoveTo(0, (LINES as u16 - 3) / 2))?
132             .queue(PrintStyledContent(format!("{:40}\r\n{:^40}\r\n{:40}", "", "Game Over! (Press 's' Restart)", "")
133                                     .with(Color::Yellow)
134                                     .on(Color::Red)
135                                     .attribute(Attribute::Bold)))?
136             ;
137         }
138 
139         stdout.queue(RestorePosition)?;
140         stdout.flush()?;
141 
142         Ok(())
143     }
144 
145     // 操作: 简单的碰撞检测, 填充, 清空
146     fn opt(&mut self, opt: ShapeOpt) -> bool
147     {
148         let (x, y) = if let ShapeOpt::COLLISION(x, y) = opt {
149             (x, y)
150         }else {
151             (0, 0)
152         };
153         let cur = &self.shapes[self.cur_shape.0][self.cur_shape.1];
154         for (i, &v) in cur.iter().enumerate() {
155             if !v { continue; }
156             let line = self.cur_position.0 + x  + ((i >> 2) as i32 );
157             let cell = self.cur_position.1 + y +  ((i & 3) as i32);
158             if line < 0 { continue; } // 未进入地图不用检测
159             if cell < 0 {return false; } // 左不能超出地图
160             let (line, cell) = (line as usize, cell as usize);
161             if cell >= self.map[0].len()  || line >= self.map.len(){ return false; } // 右/下 不能超出地图
162             
163             match opt {
164                 ShapeOpt::CLEAR => self.map[line][cell] = VALUE_EMPTY, // clear
165                 ShapeOpt::FIXED => self.map[line][cell] = VALUE_FIXED, // 固定
166                 ShapeOpt::FILL => self.map[line][cell] = VALUE_FILL, // 填充
167                 ShapeOpt::COLLISION(_, _) => {  // 碰撞
168                     if self.map[line][cell] != VALUE_EMPTY {
169                         return false;
170                     }
171                 },
172             }
173         }
174         
175         if let ShapeOpt::FIXED = opt { // 检测整行
176             let mut wholes = vec![];
177             let mut empty = 0;
178             // 从下到上检测整行, 直到空行
179             for (i, ele) in self.map.iter_mut().enumerate().rev() {
180                 if ele.iter().all(|x| *x == VALUE_FIXED) {    // 整行
181                     ele.iter_mut().for_each(|x| *x = VALUE_CLEAR);
182                     wholes.push(i);
183                 }else if ele.iter().all(|x| *x == VALUE_EMPTY) { // 空行
184                     empty = i;
185                     break;
186                 }
187             }
188             if wholes.len() > 0 {
189                 self.remove += wholes.len();
190                 // 消除动画
191                 thread::sleep(Duration::from_millis(150));
192                 self.draw().unwrap();
193                 thread::sleep(Duration::from_millis(150));
194                 for i in 0..wholes.len() {
195                     self.map[i].iter_mut().for_each(|x| {
196                         if *x == VALUE_CLEAR { *x = VALUE_EMPTY }
197                     });
198                 }
199                 self.draw().unwrap();
200                 thread::sleep(Duration::from_millis(80));
201                 // 位移
202                 let mut cursor = wholes[0];
203                 for i in 0..wholes.len() {
204                     let index1 = wholes[i];
205                     let index2 = if i + 1 == wholes.len() { empty } else { wholes[i + 1] } + 1;
206                     // (index1 ~ index2], index1本身为整行 不需要移动, 中间的都需要依次位移到cursor
207                     for j in (index2..index1).rev() {
208                         self.map[cursor] = self.map[j];
209                         self.map[j] = [0; COLS]; // 加这句就不用上部清空
210                         cursor -= 1;
211                         self.draw().unwrap();
212                         thread::sleep(Duration::from_millis(80));
213                     }
214                 }
215                 // 上部清空
216                 // for i in empty..=cursor {
217                 //     self.map[i] = [0; COLS];
218                 // }
219                 self.draw().unwrap();
220             }
221         }
222 
223         true
224     }
225 
226 
227 
228     // 移动
229     fn move_step(&mut self, step: (i32, i32)) -> bool {
230         self.opt(ShapeOpt::CLEAR);
231         if self.opt(ShapeOpt::COLLISION(step.0, step.1)) { // 碰撞检测
232             self.cur_position = (self.cur_position.0 + step.0 as i32, self.cur_position.1 + step.1);
233             self.opt(ShapeOpt::FILL);
234             return true
235         }
236         self.opt(ShapeOpt::CLEAR);
237         self.opt(ShapeOpt::FILL);
238         false
239     }
240 
241     // 开启新一轮方块
242     fn round(&mut self){
243         self.cur_position = Tetris::init_position();
244         self.cur_shape =  self.next_shape;
245         self.next_shape = Tetris::rnd();
246     }
247 
248     // 初始位置
249     fn init_position() -> (i32, i32) {
250         (-4, (COLS as i32 - 4) / 2)
251     }
252 
253     // 随机方块
254     fn rnd() -> (usize, usize) {
255         (rand::thread_rng().gen_range(0..SHAPES.len()), rand::thread_rng().gen_range(0..4))
256     }
257 
258     // 调整方块方向
259     fn direction(&mut self){
260         self.opt(ShapeOpt::CLEAR);
261         let old = self.cur_shape.1;
262         self.cur_shape.1 = (self.cur_shape.1 + 1) % 4;
263         if !self.opt(ShapeOpt::COLLISION(0, 0)) { // 碰撞检测
264             self.cur_shape.1 = old;
265         }
266         self.opt(ShapeOpt::FILL);
267     }
268 
269     // 调整下一方块朝向
270     fn next_direction(&mut self){
271         self.next_shape.1 = (self.next_shape.1 + 1) % 4;
272     }
273     // 调整下一方块
274     fn next_plus(&mut self){
275         self.next_shape = ((self.next_shape.0 + 1) % self.shapes.len(), 0);
276     }
277 
278     // 降落
279     fn fall(&mut self) {
280         if let Status::GameRunning = self.status  {
281             if !self.move_step((1, 0)) {
282                 if self.cur_position.0 == -4 {
283                     self.status = Status::GameOver;  
284                     return ()
285                 }
286                 self.opt(ShapeOpt::FIXED);
287                 self.round();
288             }
289         }
290     }
291 }
292 
293 enum Status {
294     GameStop,
295     GameRunning,
296     GameOver,
297 }
298 
299 fn main()  -> Result<()> {
300     let mut tetris = Tetris::new();    
301     let terminal_size = size()?; // 保存原始大小, 之后恢复
302     // 设置终端窗口
303     enable_raw_mode()?;
304     execute!(stdout(), 
305                         // Clear(ClearType::Purge),
306                         EnterAlternateScreen,
307                         // DisableMouseCapture,
308                         SetSize(60, 35),
309                         SetTitle("俄罗斯方块")    
310                     )?;
311      
312     let (default_interval, event_interval) = (1000, 100); // 一秒   
313     'outter: loop {
314         
315         // 事件、速度处理
316         let interval = ((default_interval as f64) / tetris.speed()) as u64;
317         let event_count = interval / event_interval;
318         for _ in 0..event_count {
319             let now = SystemTime::now();
320             // 非阻塞read事件, 这里的时间是事件的超时时间
321             if poll(Duration::from_millis(event_interval))? {
322                 // It's guaranteed that `read` wont block, because `poll` returned
323                 // `Ok(true)`.
324                 let event = read()?;
325                 if let Event::Key(KeyEvent { code, modifiers: _ }) = event {
326                     let running = if let Status::GameRunning = tetris.status { true } else { false};
327                     match code {
328                         KeyCode::Up if running => tetris.direction(),
329                         KeyCode::Left if running => { tetris.move_step((0, -1)); () },
330                         KeyCode::Right if running => { tetris.move_step((0, 1)); () },
331                         KeyCode::Down if running => tetris.fall(),
332                         KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => break 'outter,
333                         KeyCode::Char('n')  => tetris.next_direction(),
334                         KeyCode::Char('m')  => tetris.next_plus(),
335                         KeyCode::Char('s') => tetris.status = 
336                                 if running { Status::GameStop } 
337                                 else if let Status::GameOver = tetris.status { 
338                                     tetris = Tetris::new();    
339                                     Status::GameRunning
340                                 } 
341                                 else { Status::GameRunning},
342                         _ => (),
343                     }
344                     tetris.draw()?;
345                 }
346             }
347             
348             let use_time = now.elapsed().unwrap_or(Duration::from_millis(0)).subsec_millis() as u64;
349             if event_interval > use_time {
350                 thread::sleep(Duration::from_millis(event_interval - use_time));
351             }
352         }
353         // 自由落体
354         if let Status::GameRunning = tetris.status {
355             tetris.fall();
356         }
357         tetris.draw()?;
358     }
359     // 恢复窗口
360     execute!(stdout(),  LeaveAlternateScreen, SetSize(terminal_size.0, terminal_size.1))?;
361     disable_raw_mode()?;
362     Ok(())
363 }

 

尽然不支持rust高亮语法, 晕死.

crossterm支持终端高级命令(移动光标, 高亮颜色, 清理等等)

rand 随机数用到的

Cargo.toml

1 [dependencies]
2 crossterm = "0.22.1"
3 rand = "0.8.4"

 

终端显示如果不正确, 要设置一下字体

 

 

posted @ 2021-11-11 15:27  消烟客  阅读(158)  评论(0)    收藏  举报