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"
终端显示如果不正确, 要设置一下字体


浙公网安备 33010602011771号