1use crate::error::{GameError, GameResult};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6pub struct Tile {
7 pub value: u32,
9}
10
11impl Tile {
12 pub fn empty() -> Self {
14 Self { value: 0 }
15 }
16
17 pub fn new(value: u32) -> Self {
19 Self { value }
20 }
21
22 pub fn is_empty(&self) -> bool {
24 self.value == 0
25 }
26
27 pub fn can_merge_with(&self, other: &Tile) -> bool {
29 !self.is_empty() && !other.is_empty() && self.value == other.value
30 }
31
32 pub fn merge_with(&mut self, other: &Tile) -> u32 {
34 if self.can_merge_with(other) {
35 self.value *= 2;
36 self.value
37 } else {
38 0
39 }
40 }
41
42 pub fn color_index(&self) -> usize {
44 if self.is_empty() {
45 0
46 } else {
47 self.value.trailing_zeros() as usize
48 }
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct Board {
55 tiles: Vec<Vec<Tile>>,
57 size: usize,
59}
60
61impl Board {
62 pub fn new(size: usize) -> GameResult<Self> {
64 if size == 0 {
65 return Err(GameError::InvalidBoardSize { size });
66 }
67
68 let tiles = vec![vec![Tile::empty(); size]; size];
69 Ok(Self { tiles, size })
70 }
71
72 pub fn size(&self) -> usize {
74 self.size
75 }
76
77 pub fn get_tile(&self, row: usize, col: usize) -> GameResult<Tile> {
79 if row >= self.size || col >= self.size {
80 return Err(GameError::InvalidPosition { row, col });
81 }
82 Ok(self.tiles[row][col])
83 }
84
85 pub fn set_tile(&mut self, row: usize, col: usize, tile: Tile) -> GameResult<()> {
87 if row >= self.size || col >= self.size {
88 return Err(GameError::InvalidPosition { row, col });
89 }
90 self.tiles[row][col] = tile;
91 Ok(())
92 }
93
94 pub fn is_empty(&self, row: usize, col: usize) -> GameResult<bool> {
96 Ok(self.get_tile(row, col)?.is_empty())
97 }
98
99 pub fn empty_positions(&self) -> Vec<(usize, usize)> {
101 let mut positions = Vec::new();
102 for row in 0..self.size {
103 for col in 0..self.size {
104 if self.tiles[row][col].is_empty() {
105 positions.push((row, col));
106 }
107 }
108 }
109 positions
110 }
111
112 pub fn is_full(&self) -> bool {
114 self.empty_positions().is_empty()
115 }
116
117 pub fn has_valid_moves(&self) -> bool {
119 if !self.is_full() {
121 return true;
122 }
123
124 for row in 0..self.size {
126 for col in 0..self.size {
127 let current = self.tiles[row][col];
128
129 if col + 1 < self.size && current.can_merge_with(&self.tiles[row][col + 1]) {
131 return true;
132 }
133
134 if row + 1 < self.size && current.can_merge_with(&self.tiles[row + 1][col]) {
136 return true;
137 }
138 }
139 }
140
141 false
142 }
143
144 pub fn clone_board(&self) -> Self {
146 Self {
147 tiles: self.tiles.clone(),
148 size: self.size,
149 }
150 }
151
152 pub fn from_tiles(tiles: Vec<Vec<Tile>>) -> GameResult<Self> {
154 if tiles.is_empty() || tiles[0].is_empty() {
155 return Err(GameError::InvalidBoardSize { size: 0 });
156 }
157
158 let size = tiles.len();
159 if tiles.iter().any(|row| row.len() != size) {
160 return Err(GameError::InvalidBoardSize { size });
161 }
162
163 Ok(Self { tiles, size })
164 }
165
166 pub fn max_tile(&self) -> u32 {
168 self.tiles
169 .iter()
170 .flat_map(|row| row.iter())
171 .map(|tile| tile.value)
172 .max()
173 .unwrap_or(0)
174 }
175
176 pub fn count_tiles(&self, value: u32) -> usize {
178 self.tiles
179 .iter()
180 .flat_map(|row| row.iter())
181 .filter(|tile| tile.value == value)
182 .count()
183 }
184
185 pub fn to_vec(&self) -> Vec<Vec<u32>> {
187 self.tiles
188 .iter()
189 .map(|row| row.iter().map(|tile| tile.value).collect())
190 .collect()
191 }
192
193 pub fn from_vec(values: Vec<Vec<u32>>) -> GameResult<Self> {
195 if values.is_empty() || values[0].is_empty() {
196 return Err(GameError::InvalidBoardSize { size: 0 });
197 }
198
199 let size = values.len();
200 if values.iter().any(|row| row.len() != size) {
201 return Err(GameError::InvalidBoardSize { size });
202 }
203
204 let tiles = values
205 .iter()
206 .map(|row| row.iter().map(|&value| Tile::new(value)).collect())
207 .collect();
208
209 Ok(Self { tiles, size })
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_board_creation() {
219 let board = Board::new(4).unwrap();
220 assert_eq!(board.size(), 4);
221 assert!(board.is_empty(0, 0).unwrap());
222 }
223
224 #[test]
225 fn test_invalid_board_size() {
226 assert!(Board::new(0).is_err());
227 }
228
229 #[test]
230 fn test_tile_operations() {
231 let mut tile = Tile::empty();
232 assert!(tile.is_empty());
233
234 tile = Tile::new(2);
235 assert!(!tile.is_empty());
236 assert_eq!(tile.value, 2);
237
238 let other = Tile::new(2);
239 assert!(tile.can_merge_with(&other));
240 assert_eq!(tile.merge_with(&other), 4);
241 }
242
243 #[test]
244 fn test_board_operations() {
245 let mut board = Board::new(4).unwrap();
246
247 board.set_tile(0, 0, Tile::new(2)).unwrap();
249 assert_eq!(board.get_tile(0, 0).unwrap().value, 2);
250
251 assert!(board.get_tile(4, 0).is_err());
253 assert!(board.set_tile(0, 4, Tile::new(2)).is_err());
254
255 let empty = board.empty_positions();
257 assert_eq!(empty.len(), 15); assert_eq!(board.max_tile(), 2);
261 }
262}