tetris-demo A Tetris example written in Rust using Piston in under 500 lines of code
项目地址: https://gitcode.com/gh_mirrors/te/tetris-demo
项目介绍
"Tetris Example in Rust, v2" 是一个用Rust语言编写的俄罗斯方块游戏示例。这个项目不仅是一个简单的游戏实现,更是一个展示Rust编程基础的绝佳范例。通过414行代码,开发者可以深入了解Rust的基本语法和编程思想。此外,项目还提供了一个清晰的Git历史记录,展示了功能的逐步迭代过程,非常适合初学者和有经验的开发者学习参考。
完整代码
use piston_window::{WindowSettings, PistonWindow, Event, RenderEvent, PressEvent};
use piston_window::{Rectangle, DrawState, Context, Graphics};
use piston_window::{Button, Key};
use rand::Rng;
use std::time::{Duration, Instant};
use std::collections::HashMap;
enum DrawEffect<'a> {
None,
Darker,
Flash(&'a Vec<i8>),
}
#[derive(Copy, Clone)]
enum Color {
Red, Green, Blue, Magenta, Cyan, Yellow, Orange,
}
#[derive(Default, Clone)]
struct Board(HashMap<(i8, i8), Color>);
impl Board {
fn new(v: &[(i8, i8)], color: Color) -> Self {
Board(v.iter().cloned().map(|(x, y)| ((x, y), color)).collect())
}
fn modified<F>(&self, f: F) -> Self
where F: Fn((i8, i8)) -> (i8, i8)
{
Board(self.0.iter().map(|((x, y), color)| (f((*x, *y)), *color)).collect())
}
fn modified_filter<F>(&self, f: F) -> Self
where F: Fn((i8, i8)) -> Option<(i8, i8)>
{
Board(self.0.iter()
.filter_map(|((x, y), color)| f((*x, *y)).map(|p| (p, *color)))
.collect())
}
fn transposed(&self) -> Self {
self.modified(|(ox, oy)| (oy, ox))
}
fn mirrored_y(&self) -> Self {
self.modified(|(ox, oy)| (ox, -oy))
}
fn rotated(&self) -> Self {
self.mirrored_y().transposed()
}
fn rotated_counter(&self) -> Self {
self.rotated().rotated().rotated()
}
fn negative_shift(&self) -> (i8, i8) {
use std::cmp::min;
self.0.keys().into_iter().cloned()
.fold((0, 0), |(mx, my), (ox, oy)| (min(mx, ox), min(my, oy)))
}
fn shifted(&self, (x, y): (i8, i8)) -> Self {
self.modified(|(ox, oy)| (ox + x, oy + y))
}
fn merged(&self, other: &Board) -> Option<Self> {
let mut hashmap = HashMap::new();
hashmap.extend(other.0.iter());
hashmap.extend(self.0.iter());
if hashmap.len() != self.0.len() + other.0.len() {
return None;
}
Some(Self(hashmap))
}
fn contained(&self, x: i8, y: i8) -> bool {
self.0.keys().into_iter().cloned()
.fold(true, |b, (ox, oy)| b && ox < x && oy < y && ox >= 0 && oy >= 0)
}
fn whole_lines(&self, x: i8, y: i8) -> Vec<i8> {
let mut idxs = vec![];
for oy in 0 .. y {
if (0 .. x).filter_map(|ox| self.0.get(&(ox, oy))).count() == x as usize {
idxs.push(oy)
}
}
idxs
}
fn kill_line(&self, y: i8) -> Self {
self.modified_filter(|(ox, oy)|
if oy > y {
Some((ox, oy))
} else if oy == y {
None
} else {
Some((ox, oy + 1))
}
)
}
fn render<'a, G>(
&self,
metrics: &Metrics,
c: &Context,
g: &mut G,
draw_effect: DrawEffect<'a>,
)
where G: Graphics
{
let mut draw = |color, rect: [f64; 4]| {
Rectangle::new(color).draw(rect, &DrawState::default(), c.transform, g);
};
for x in 0 .. metrics.board_x {
for y in 0 .. metrics.board_y {
let block_pixels = metrics.block_pixels as f64;
let border_size = block_pixels / 20.0;
let outer = [block_pixels * (x as f64), block_pixels * (y as f64), block_pixels, block_pixels];
let inner = [outer[0] + border_size, outer[1] + border_size,
outer[2] - border_size * 2.0, outer[3] - border_size * 2.0];
draw([0.2, 0.2, 0.2, 1.0], outer);
draw([0.1, 0.1, 0.1, 1.0], inner);
if let Some(color) = self.0.get(&(x as i8, y as i8)) {
let code = match color {
Color::Red => [1.0, 0.0, 0.0, 1.0],
Color::Green => [0.0, 1.0, 0.0, 1.0],
Color::Blue => [0.5, 0.5, 1.0, 1.0],
Color::Magenta => [1.0, 0.0, 1.0, 1.0],
Color::Cyan => [0.0, 1.0, 1.0, 1.0],
Color::Yellow => [1.0, 1.0, 0.0, 1.0],
Color::Orange => [1.0, 0.5, 0.0, 1.0],
};
draw(code, outer);
let code = [code[0]*0.8, code[1]*0.8, code[2]*0.8, code[3]];
draw(code, inner);
}
match draw_effect {
DrawEffect::None => {},
DrawEffect::Flash(lines) => {
if lines.contains(&(y as i8)) {
draw([1.0, 1.0, 1.0, 0.5], outer);
}
}
DrawEffect::Darker => {
draw([0.0, 0.0, 0.0, 0.9], outer);
}
}
}
}
}
}
#[derive(Default)]
struct Metrics {
block_pixels: usize,
board_x: usize,
board_y: usize,
}
impl Metrics {
fn resolution(&self) -> [u32; 2] {
[(self.board_x * self.block_pixels) as u32,
(self.board_y * self.block_pixels) as u32]
}
}
enum State {
Flashing(isize, Instant, Vec<i8>),
Falling(Board),
GameOver,
}
struct Game {
board: Board,
metrics: Metrics,
state: State,
shift: (i8, i8),
possible_pieces: Vec<Board>,
time_since_fall: Instant,
}
impl Game {
fn new(metrics: Metrics) -> Self {
Self {
metrics,
board: Default::default(),
state: State::Falling(Default::default()),
time_since_fall: Instant::now(),
shift: (0, 0),
possible_pieces: vec![
Board::new(&[(0, 0), (0, 1), (1, 0), (1, 1), ][..], Color::Red),
Board::new(&[(0, 0), (1, 0), (1, 1), (2, 0), ][..], Color::Green),
Board::new(&[(0, 0), (1, 0), (2, 0), (3, 0), ][..], Color::Blue),
Board::new(&[(0, 0), (1, 0), (2, 0), (0, 1), ][..], Color::Orange),
Board::new(&[(0, 0), (1, 0), (2, 0), (2, 1), ][..], Color::Yellow),
Board::new(&[(0, 0), (1, 0), (1, 1), (2, 1), ][..], Color::Cyan),
Board::new(&[(1, 0), (2, 0), (0, 1), (1, 1), ][..], Color::Magenta),
]
}
}
fn new_falling(&mut self) {
let mut rng = rand::thread_rng();
let idx = rng.gen_range(0, self.possible_pieces.len());
self.state = State::Falling(s