1use std::{error::Error, fmt::Display};
2
3use super::{
4 controller::{ControllerError, Move},
5 grid::Grid,
6};
7
8#[derive(Debug, Clone)]
9pub struct Rules {
10 size: usize,
11 spawn_per_turn: usize,
12}
13
14impl Rules {
15 pub fn size(mut self, size: usize) -> Self {
16 self.size = size;
17 self
18 }
19
20 pub fn spawn_per_turn(mut self, spawn_per_turn: usize) -> Self {
21 self.spawn_per_turn = spawn_per_turn;
22 self
23 }
24}
25
26impl Default for Rules {
27 fn default() -> Self {
28 Self {
29 size: 4,
30 spawn_per_turn: 1,
31 }
32 }
33}
34
35#[derive(Debug)]
36pub enum GameError {
37 GridIsFull,
38 ControllerError(ControllerError),
39}
40
41impl From<ControllerError> for GameError {
42 fn from(error: ControllerError) -> Self {
43 Self::ControllerError(error)
44 }
45}
46
47impl Display for GameError {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self {
50 Self::GridIsFull => f.write_str("grid is full"),
51 GameError::ControllerError(err) => err.fmt(f),
52 }
53 }
54}
55
56impl Error for GameError {}
57
58#[derive(Clone)]
59pub struct Game {
60 board: Grid,
61 score: usize,
62 turn_index: usize,
63 spawn_per_turn: usize,
64 rules: Rules,
65}
66
67impl Game {
68 pub fn new(rules: Rules) -> Self {
69 let Rules {
70 size,
71 spawn_per_turn,
72 } = rules;
73
74 Self {
75 board: Grid::new(size),
76 score: 0,
77 turn_index: 0,
78 spawn_per_turn,
79 rules,
80 }
81 }
82
83 pub fn get_board(&self) -> &Grid {
84 &self.board
85 }
86
87 pub fn get_score(&self) -> usize {
88 self.score
89 }
90
91 pub fn get_rules(&self) -> &Rules {
92 &self.rules
93 }
94
95 pub fn get_turn_index(&self) -> usize {
96 self.turn_index
97 }
98
99 pub fn turn(&mut self, movement: Move) -> Result<(), GameError> {
100 self.perform_move(movement);
101 for _ in 0..self.spawn_per_turn {
102 self.spawn_random()?;
103 }
104 self.turn_index += 1;
105 Ok(())
106 }
107
108 fn spawn_random(&mut self) -> Result<(), GameError> {
109 let mut potentials = vec![];
110 for x in 0..self.board.size() {
111 for y in 0..self.board.size() {
112 if self
113 .board
114 .get((x, y))
115 .expect("coordinates are valid")
116 .is_empty()
117 {
118 potentials.push((x, y));
119 }
120 }
121 }
122 let potential_count = potentials.len() as f32;
123 if potential_count == 0. {
124 return Err(GameError::GridIsFull);
125 }
126 let random = rand::random::<f32>() * potential_count;
127 let index = random.floor() as usize;
128 let (x, y) = potentials[index];
129 self.board.set((x, y), Some(1));
130 Ok(())
131 }
132
133 pub fn perform_move(&mut self, movement: Move) -> usize {
134 let mut move_score = 0;
135 match movement {
136 Move::LEFT => {
137 for y in 0..self.board.size() {
138 for x in 0..self.board.size() {
139 move_score += self.perform_linear_move((-1, 0), (x, y));
140 }
141 }
142 }
143 Move::RIGHT => {
144 for y in 0..self.board.size() {
145 for x in (0..self.board.size()).rev() {
146 move_score += self.perform_linear_move((1, 0), (x, y));
147 }
148 }
149 }
150 Move::UP => {
151 for x in 0..self.board.size() {
152 for y in 0..self.board.size() {
153 move_score += self.perform_linear_move((0, -1), (x, y));
154 }
155 }
156 }
157 Move::DOWN => {
158 for x in 0..self.board.size() {
159 for y in (0..self.board.size()).rev() {
160 move_score += self.perform_linear_move((0, 1), (x, y));
161 }
162 }
163 }
164 };
165 self.score += move_score;
166 move_score
167 }
168
169 fn perform_linear_move(
170 &mut self,
171 direction: (isize, isize),
172 tile_pos: (usize, usize),
173 ) -> usize {
174 if self
175 .board
176 .get(tile_pos)
177 .expect("function should only be called internally with known coordinates")
178 .is_empty()
179 {
180 0
181 } else {
182 let mut displacement = Displacement::new(&mut self.board, tile_pos, direction);
183 displacement.move_all();
184 displacement.pop_score()
185 }
186 }
187}
188
189pub struct Displacement<'g> {
190 grid: &'g mut Grid,
191 position: (usize, usize),
192 direction: (isize, isize),
193 score: usize,
194}
195
196impl<'g> Displacement<'g> {
197 pub fn new(grid: &'g mut Grid, position: (usize, usize), direction: (isize, isize)) -> Self {
198 Self {
199 grid,
200 position,
201 direction,
202 score: 0,
203 }
204 }
205
206 pub fn pop_score(self) -> usize {
207 let Displacement { score, .. } = self;
208 score
209 }
210
211 pub fn move_all(&mut self) {
212 loop {
213 let can_continue = self.move_once();
214 if !can_continue {
215 break;
216 }
217 }
218 }
219
220 fn move_once(&mut self) -> bool {
221 let current_pos = self.position;
222 let current_value = self
223 .grid
224 .get_val(current_pos)
225 .expect("last position should be valid");
226 if let Some(next_pos) = self.get_next_pos() {
227 match self.grid.get_val(next_pos) {
228 None => {
229 self.grid.move_tile(current_pos, next_pos);
230 self.set_pos(next_pos);
231 true
232 }
233 Some(value) if value == current_value => {
234 self.grid.move_tile(current_pos, next_pos);
235 self.grid.set(next_pos, Some(value * 2));
236 self.score = value * 2;
237 false
238 }
239 Some(_) => false,
240 }
241 } else {
242 false
243 }
244 }
245
246 fn get_next_pos(&self) -> Option<(usize, usize)> {
247 let (current_x, current_y) = self.position;
248 let (dx, dy) = self.direction;
249 if would_overflow(current_x, dx, self.grid.size() - 1)
250 || would_overflow(current_y, dy, self.grid.size() - 1)
251 {
252 None
253 } else {
254 let next_x = (current_x as isize) + dx;
255 let next_y = (current_y as isize) + dy;
256 Some((next_x as usize, next_y as usize))
257 }
258 }
259
260 fn set_pos(&mut self, (x, y): (usize, usize)) {
261 self.position = (x, y);
262 }
263}
264
265fn would_overflow(number: usize, delta: isize, max: usize) -> bool {
267 let too_little = number == 0 && delta == -1;
268 let too_big = number == max && delta == 1;
269 too_little || too_big
270}