1use circular_buffer::CircularBuffer;
4
5use rand::seq::SliceRandom;
6
7use crate::cards::{new_standard_deck, Card};
8
9const RANKS: u8 = 13;
11const SUITS: u8 = 4;
13const DECK_SIZE: usize = RANKS as usize * SUITS as usize;
15
16const HEARTS: u8 = 1;
18const CLUBS: u8 = 2;
20const DIAMONDS: u8 = 3;
22const SPADES: u8 = 4;
24
25const FOUNDATIONS: usize = SUITS as usize;
27const FREE_CELLS: usize = 4;
29const TABLEAU_SIZE: usize = 8;
31const FIELD_SIZE: usize = FOUNDATIONS + FREE_CELLS + TABLEAU_SIZE;
33
34const UNDO_LEVELS: usize = 1000;
36
37#[derive(Default, Copy, Clone)]
39struct Move {
40 from: usize,
42 to: usize
44}
45
46pub struct Game {
48 field: [Vec<Card>; FIELD_SIZE],
50
51 highlighted_card: usize,
53
54 selected_card_opt: Option<usize>,
56
57 undo_history: CircularBuffer<UNDO_LEVELS, Move>,
59
60 move_count: u32,
62
63 high_contrast: bool,
65}
66
67impl Game {
68 pub fn new(rng: &mut rand::rngs::ThreadRng) -> Game {
78 let mut game = Game {
79 field: core::array::from_fn(|_| Vec::with_capacity(DECK_SIZE)),
80 highlighted_card: FOUNDATIONS + FREE_CELLS,
81 selected_card_opt: None,
82 undo_history: CircularBuffer::new(),
83 move_count: 0,
84 high_contrast: false
85 };
86
87 let mut deck = new_standard_deck(RANKS, SUITS);
89 deck.shuffle(rng);
90 for (i, card) in deck.into_iter().enumerate() {
93 let field_column = FOUNDATIONS + FREE_CELLS + (i % TABLEAU_SIZE);
94 game.field[field_column].push(card);
95 }
96
97 game
98 }
99
100 pub fn is_won(&self) -> bool {
106 self.field.iter().take(FOUNDATIONS).all(|stack| stack.len() == RANKS as usize)
108 }
109
110 pub fn toggle_high_contrast(&mut self) {
112 self.high_contrast = !self.high_contrast;
113 }
114
115 pub fn move_cursor_left(&mut self) {
117 self.highlighted_card = (self.highlighted_card + FIELD_SIZE - 1) % FIELD_SIZE;
119
120 match self.selected_card_opt {
121 Some(selected_card) => {
122 while !self.move_is_valid(selected_card, self.highlighted_card) && selected_card != self.highlighted_card {
123 self.move_cursor_left();
124 }
125 }
126 None => {
127 while self.field[self.highlighted_card].last().is_none() {
128 self.move_cursor_left();
129 }
130 }
131 }
132 }
133
134 pub fn move_cursor_right(&mut self) {
136 self.highlighted_card = (self.highlighted_card + 1) % FIELD_SIZE;
137
138 match self.selected_card_opt {
139 Some(selected_card) => {
140 while !self.move_is_valid(selected_card, self.highlighted_card) && selected_card != self.highlighted_card {
141 self.move_cursor_right();
142 }
143 }
144 None => {
145 while self.field[self.highlighted_card].last().is_none() {
146 self.move_cursor_right();
147 }
148 }
149 }
150 }
151
152 pub fn quick_stack_to_foundations(&mut self) {
154 let mut made_move = false;
155
156 'outer: for source_column in 0..self.field.len() {
157 for target_column in 0..FOUNDATIONS {
158 if self.move_is_valid(source_column, target_column) {
159 self.player_try_execute_move(source_column, target_column);
160 made_move = true;
161 break 'outer;
162 }
163 }
164 }
165 if made_move {self.quick_stack_to_foundations()};
167 }
168
169 pub fn handle_card_press(&mut self) {
171 if self.selected_card_opt.is_none() {
172 self.selected_card_opt = Some(self.highlighted_card);
174 } else if Some(self.highlighted_card) == self.selected_card_opt {
175 self.selected_card_opt = None;
177 } else {
178 if let Some(selected_card) = self.selected_card_opt {
180 self.player_try_execute_move(selected_card, self.highlighted_card);
181 }
182 }
183 }
184
185 pub fn player_try_execute_move(&mut self, from: usize, to: usize) {
187 if self.move_is_valid(from, to) {
188 self.execute_move(from, to);
190 self.move_count += 1;
191 self.undo_history.push_back(Move{from, to});
192 }
193 }
194
195 pub fn perform_undo(&mut self) {
198 let last_move_opt = self.undo_history.pop_back();
199 if let Some(last_move) = last_move_opt {
200 self.execute_move(last_move.to, last_move.from);
201 self.move_count -= 1;
202 } }
204
205 fn are_opposite_colors(card1: Card, card2: Card) -> bool {
207 if card1.suit == HEARTS || card1.suit == DIAMONDS {return card2.suit == SPADES || card2.suit == CLUBS};
208 if card1.suit == SPADES || card1.suit == CLUBS {return card2.suit == HEARTS || card2.suit == DIAMONDS};
209 false
210 }
211
212 fn move_is_valid(&self, from: usize, to: usize) -> bool {
214 if from == to {return false;};
215 let from_top_card = self.field[from].last().copied().unwrap_or_default();
216 let to_top_card = self.field[to].last().copied().unwrap_or_default();
217 if to < FOUNDATIONS {
218 if to_top_card.rank != 0 {
220 return from_top_card.rank == to_top_card.rank + 1 && from_top_card.suit == to_top_card.suit;
221 }
222 return from_top_card.rank == 1 && to == (from_top_card.suit - 1) as usize;
223 } else if to < FOUNDATIONS + FREE_CELLS {
224 return to_top_card.rank == 0;
226 } else if to < FOUNDATIONS + FREE_CELLS + TABLEAU_SIZE {
227 if to_top_card.rank != 0 {
229 return from_top_card.rank == to_top_card.rank - 1 && Game::are_opposite_colors(from_top_card, to_top_card);
230 }
231 return true;
232 }
233 false
234 }
235
236 fn execute_move (&mut self, from: usize, to: usize) {
239 let from_card_opt = self.field[from].last();
242 if let Some(&from_card) = from_card_opt {
243 self.field[from].pop();
244 self.field[to].push(from_card);
245 }
246 self.selected_card_opt = None;
247 }
248}
249
250mod print;