效果演示
本示例涉及的主要知识点:
- eframe 跨平台配置
- egui 的基本用法
- Rust 语言条件编译
- egui 基本控件用法
- egui 布局
- egui 图形绘制技术
- 键盘事件响应
- 按钮事件响应
- 嵌入字体
部分源码如下:
use crate::pages::gameview::settings::{
BACKGROUND_COLOR, BLOCK_COLOR, BLOCK_SIZE, GRID_HEIGHT, GRID_WIDTH,
};
use crate::pages::gameview::shape;
use eframe::egui::{Rect, Ui, Vec2};
use getrandom;
pub struct Tetris {
grid: Vec<Vec<u8>>,
current_tetromino: shape::Tetromino,
current_shape: [[u8; 4]; 4], // Store the current shape directly
position: (i32, i32),
}
impl Tetris {
pub fn new() -> Self {
let tetromino = shape::Tetromino::T;
Self {
grid: vec![vec![0; GRID_WIDTH]; GRID_HEIGHT],
current_tetromino: tetromino,
current_shape: tetromino.shape(),
position: (0, 3),
}
}
pub fn update(&mut self) {
self.position.0 += 1;
if self.check_collision() {
self.position.0 -= 1;
self.place_tetromino();
self.clear_lines();
self.spawn_new_tetromino();
}
}
fn check_collision(&self) -> bool {
for y in 0..4 {
for x in 0..4 {
if self.current_shape[y][x] == 1 {
let grid_x = self.position.1 + x as i32;
let grid_y = self.position.0 + y as i32;
// Check if the tetromino is out of bounds or collides with existing blocks
if grid_x < 0
|| grid_x >= GRID_WIDTH as i32
|| grid_y >= GRID_HEIGHT as i32
|| (grid_y >= 0 && self.grid[grid_y as usize][grid_x as usize] == 1)
{
return true;
}
}
}
}
false
}
fn place_tetromino(&mut self) {
for y in 0..4 {
for x in 0..4 {
if self.current_shape[y][x] == 1 {
let grid_x = self.position.1 + x as i32;
let grid_y = self.position.0 + y as i32;
if grid_y >= 0 && grid_x >= 0 {
self.grid[grid_y as usize][grid_x as usize] = 1;
}
}
}
}
}
fn clear_lines(&mut self) {
self.grid.retain(|row| row.iter().any(|&cell| cell == 0));
while self.grid.len() < GRID_HEIGHT {
self.grid.insert(0, vec![0; GRID_WIDTH]);
}
}
fn spawn_new_tetromino(&mut self) {
let shapes = [
shape::Tetromino::I,
shape::Tetromino::O,
shape::Tetromino::T,
shape::Tetromino::S,
shape::Tetromino::Z,
shape::Tetromino::J,
shape::Tetromino::L,
];
let mut buf = [0u8; 1];
getrandom::fill(&mut buf).expect("Failed to generate random number");
let index = (buf[0] as usize) % shapes.len();
self.current_tetromino = shapes[index];
self.current_shape = self.current_tetromino.shape();
self.position = (0, 3);
}
pub fn render(&self, ui: &mut Ui) {
let rect = ui.available_rect_before_wrap();
let center = rect.center();
let grid_size = Vec2::new(
GRID_WIDTH as f32 * (BLOCK_SIZE + 2.0),
GRID_HEIGHT as f32 * (BLOCK_SIZE + 2.0),
);
let mut top_left = center - grid_size / 2.0;
top_left.y -= 35.0;
// Draw the grid
for y in 0..GRID_HEIGHT {
for x in 0..GRID_WIDTH {
let color = if self.grid[y][x] == 1 {
BLOCK_COLOR
} else {
BACKGROUND_COLOR
};
let block_rect = Rect::from_min_size(
top_left
+ Vec2::new(x as f32 * (BLOCK_SIZE + 2.0), y as f32 * (BLOCK_SIZE + 2.0)),
Vec2::splat(BLOCK_SIZE),
);
ui.painter().rect_filled(block_rect, 0.0, color);
}
}
// Draw the current moving tetromino
for y in 0..4 {
for x in 0..4 {
if self.current_shape[y][x] == 1 {
let grid_x = self.position.1 + x as i32;
let grid_y = self.position.0 + y as i32;
if grid_y >= 0
&& grid_x >= 0
&& grid_x < GRID_WIDTH as i32
&& grid_y < GRID_HEIGHT as i32
{
let block_rect = Rect::from_min_size(
top_left
+ Vec2::new(
grid_x as f32 * (BLOCK_SIZE + 2.0),
grid_y as f32 * (BLOCK_SIZE + 2.0),
),
Vec2::splat(BLOCK_SIZE),
);
ui.painter().rect_filled(block_rect, 0.0, BLOCK_COLOR);
}
}
}
}
}
pub fn move_horizontal(&mut self, direction: i32) {
self.position.1 += direction;
if self.check_collision() {
self.position.1 -= direction; // Revert if collision occurs
}
}
pub fn rotate(&mut self) {
let mut new_shape = [[0; 4]; 4];
// Rotate the shape 90 degrees clockwise
for y in 0..4 {
for x in 0..4 {
new_shape[x][3 - y] = self.current_shape[y][x];
}
}
// Check for collision after rotation
let original_shape = self.current_shape;
self.current_shape = new_shape;
if self.check_collision() {
self.current_shape = original_shape; // Revert if collision occurs
}
}
}