blocks_lib/
board.rs

1///
2/// The board module contains the logic for the game board and the pieces.
3/// The board is represented as a 2D array of u8, where 0 is an empty cell and any other value is a piece.
4/// 255 is a special value used to draw the current piece. 254 is the tracer piece.
5///
6use rand::seq::SliceRandom;
7
8use crate::pieces::{xy, Piece, PieceColor, PIECES};
9
10///
11/// The board is represented as a 2D array of u8, where PieceColor::Empty is an empty cell and any other value is a piece.
12///
13#[derive(Clone)]
14pub struct Board {
15    pub width: u16,
16    pub height: u16,
17    pub cells: Vec<Vec<PieceColor>>,
18}
19
20///
21/// An implementation of the 7-bag randomizer. Pieces are drawn from a bag of 7 pieces,
22/// and when the bag is empty, a new bag is created and shuffled.
23///
24#[derive(Clone)]
25pub struct Bag {
26    pieces: Vec<Piece>,
27}
28
29impl Bag {
30    pub fn new() -> Bag {
31        let mut pieces = PIECES.to_vec();
32        let mut rng = rand::thread_rng();
33        pieces.shuffle(&mut rng);
34        Bag { pieces }
35    }
36
37    pub fn next(&mut self) -> Piece {
38        if self.pieces.len() == 0 {
39            self.pieces = PIECES.to_vec();
40            let mut rng = rand::thread_rng();
41            self.pieces.shuffle(&mut rng);
42        }
43        // This is safe because we just checked the length.
44        self.pieces.pop().unwrap()
45    }
46}
47
48///
49/// The current piece in the game, that is, a piece that is currently being moved by the player and has not
50/// been committed to the board yet.
51///
52#[derive(Clone, Debug)]
53pub struct CurrentPiece {
54    pub piece: Piece,
55    pub x: i32,
56    pub y: i32,
57}
58
59///
60/// Clears any lines that are full and moves the lines above down.
61///
62pub fn clear_lines(board: &mut Board) -> i32 {
63    let mut y = board.height as usize - 2;
64    let mut lines = 0;
65    while y > 0 {
66        if (0..board.width).all(|x| board.cells[x as usize][y] != PieceColor::Empty) {
67            lines += 1;
68            for y2 in (1..=y).rev() {
69                for x in 0..board.width {
70                    board.cells[x as usize][y2] = board.cells[x as usize][y2 - 1];
71                }
72            }
73        } else {
74            y -= 1;
75        }
76    }
77    lines
78}
79
80///
81/// Draws the piece on the board.
82///
83pub fn draw_piece(piece: &Piece, board: &mut Board, x: i32, y: i32, color: PieceColor) {
84    for square in piece.view() {
85        let x = xy(&square).0 as i32 + x as i32;
86        let y = xy(&square).1 as i32 + y as i32;
87        board.cells[x as usize][y as usize] = color;
88    }
89}
90
91///
92/// Removes the piece from the board.
93///
94pub fn remove_piece(piece: &Piece, board: &mut Board, x: i32, y: i32) {
95    for square in piece.view() {
96        let x = xy(&square).0 as i32 + x as i32;
97        let y = xy(&square).1 as i32 + y as i32;
98        board.cells[x as usize][y as usize] = PieceColor::Empty;
99    }
100}
101
102///
103/// Removes the tracer from the board.
104///
105pub fn remove_tracer(board: &mut Board) {
106    // Clear out the current position of the piece, if any.
107    for y in 0..board.height {
108        for x in 0..board.width {
109            if board.cells[x as usize][y as usize] == PieceColor::Tracer {
110                board.cells[x as usize][y as usize] = PieceColor::Empty;
111            }
112        }
113    }
114}
115
116///
117/// Draws the tracer piece on the board.
118///
119pub fn draw_tracer(piece: &Piece, board: &mut Board, x: i32, y: i32) {
120    // Clear out the current position of the piece, if any.
121    remove_tracer(board);
122    for square in piece.view() {
123        let x = xy(&square).0 as i32 + x as i32;
124        let y = xy(&square).1 as i32 + y as i32;
125        board.cells[x as usize][y as usize] = PieceColor::Tracer;
126    }
127}
128
129impl CurrentPiece {
130    ///
131    /// Detects if the position of the piece collides with existing squares on the board.
132    ///
133    /// The piece cannot intersect with the walls or the bottom of the board, and it cannot intersect with
134    /// existing squares on the board.
135    ///
136    pub fn collides(&self, board: &Board, x: i32, y: i32) -> bool {
137        for square in self.piece.view() {
138            let dx = xy(&square).0 as i32;
139            let dy = xy(&square).1 as i32;
140            let x = dx as i32 + x as i32;
141            let y = dy as i32 + y as i32;
142
143            if x < 0 || x >= board.width as i32 || y >= board.height as i32 {
144                return true;
145            }
146            if y >= 0
147                && board.cells[x as usize][y as usize] != PieceColor::Empty
148                && board.cells[x as usize][y as usize] != PieceColor::Tracer
149            {
150                return true;
151            }
152        }
153        false
154    }
155
156    ///
157    /// Rotate this current piece right
158    /// Returns true if the rotation was successful, false if it was not.
159    ///
160    pub fn rotate_right(&mut self, board: &Board) -> bool {
161        self.piece.rotate_right();
162        if self.collides(board, self.x, self.y) {
163            self.piece.rotate_left();
164            return false;
165        }
166        true
167    }
168
169    ///
170    /// Move this current piece to the left
171    /// Returns true if the move was successful, false if it was not.
172    ///
173    pub fn move_left(&mut self, board: &Board) -> bool {
174        let x = self.x - 1;
175        if !self.collides(board, x, self.y) {
176            self.x = x;
177            return true;
178        }
179        false
180    }
181
182    ///
183    /// Move this current piece to the right
184    /// Returns true if the move was successful, false if it was not.
185    ///
186    pub fn move_right(&mut self, board: &Board) -> bool {
187        let x = self.x + 1;
188        if !self.collides(board, x, self.y) {
189            self.x = x;
190            return true;
191        }
192        false
193    }
194
195    ///
196    /// Move this current piece down
197    /// Returns true if the move was successful, false if it was not.
198    ///
199    pub fn move_down(&mut self, board: &Board) -> bool {
200        let y = self.y + 1;
201        if !self.collides(board, self.x, y) {
202            self.y = y;
203            return true;
204        }
205        false
206    }
207}