rs48_lib/
game.rs

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
265/// determine if the given number, added a delta that is either 1 or -1 to it, would overflow a certain maximum value for n
266fn 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}