1 // This file is part of patsolve. It is subject to the license terms in
2 // the LICENSE file found in the top-level directory of this distribution
3 // and at https://github.com/shlomif/patsolve/blob/master/LICENSE . No
4 // part of patsolve, including this file, may be copied, modified, propagated,
5 // or distributed except according to the terms contained in the COPYING file.
6 //
7 // Copyright (c) 2002 Tom Holroyd
8 #include "rinutils/count.h"
9 #include "instance.h"
10
calc_empty_col_idx(fcs_pats_thread * const soft_thread,const int stacks_num)11 static inline int calc_empty_col_idx(
12 fcs_pats_thread *const soft_thread, const int stacks_num)
13 {
14 for (int w = 0; w < stacks_num; w++)
15 {
16 if (fcs_state_col_is_empty(soft_thread->current_pos.s, w))
17 {
18 return w;
19 }
20 }
21 return -1;
22 }
23
24 // Automove logic. Freecell games must avoid certain types of automoves.
good_automove(fcs_pats_thread * const soft_thread,const int o,const int r)25 static inline bool good_automove(
26 fcs_pats_thread *const soft_thread, const int o, const int r)
27 {
28 FCS_ON_NOT_FC_ONLY(
29 const fcs_instance *const instance = soft_thread->instance);
30
31 if (
32 #ifndef FCS_FREECELL_ONLY
33 (GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) ==
34 FCS_SEQ_BUILT_BY_SUIT) ||
35 #endif
36 r <= 2)
37 {
38 return true;
39 }
40
41 // Check the Out piles of opposite color.
42 for (int i = 1 - (o & 1); i < 4; i += 2)
43 {
44 if (fcs_foundation_value(soft_thread->current_pos.s, i) < r - 1)
45 {
46 #if 1 /* Raymond's Rule */
47 /* Not all the N-1's of opposite color are out
48 yet. We can still make an automove if either
49 both N-2's are out or the other same color N-3
50 is out (Raymond's rule). Note the re-use of
51 the loop variable i. We return here and never
52 make it back to the outer loop. */
53
54 for (int j = 1 - (o & 1); j < 4; j += 2)
55 {
56 if (fcs_foundation_value(soft_thread->current_pos.s, j) < r - 2)
57 {
58 return false;
59 }
60 }
61 return (fcs_foundation_value(
62 soft_thread->current_pos.s, ((o + 2) & 3)) >= r - 3);
63 #else /* Horne's Rule */
64 return false;
65 #endif
66 }
67 }
68
69 return true;
70 }
71 /* Get the possible moves from a position, and store them in
72 * soft_thread->possible_moves[]. */
73
get_possible_moves(fcs_pats_thread * const soft_thread,bool * const a,int * const num_cards_out)74 static inline int get_possible_moves(
75 fcs_pats_thread *const soft_thread, bool *const a, int *const num_cards_out)
76 {
77 FCS_ON_NOT_FC_ONLY(
78 const fcs_instance *const instance = soft_thread->instance);
79 DECLARE_STACKS();
80
81 /* Check for moves from soft_thread->current_pos.stacks to
82 * soft_thread->current_pos.foundations. */
83
84 #define NUM_MOVES (move_ptr - soft_thread->possible_moves)
85 var_PTR(move_ptr, soft_thread->possible_moves);
86 for (int w = 0; w < LOCAL_STACKS_NUM; ++w)
87 {
88 const_AUTO(col, fcs_state_get_col(soft_thread->current_pos.s, w));
89 const int col_len = fcs_col_len(col);
90 if (!col_len)
91 {
92 continue;
93 }
94 const fcs_card card = fcs_col_get_card(col, col_len - 1);
95 const int o = fcs_card_suit(card);
96 if (fcs_card_rank(card) ==
97 fcs_foundation_value(soft_thread->current_pos.s, o) + 1)
98 {
99 *(move_ptr++) = (typeof(*move_ptr)){.card = card,
100 .from = (unsigned char)w,
101 .fromtype = FCS_PATS__TYPE_WASTE,
102 .to = (unsigned char)o,
103 .totype = FCS_PATS__TYPE_FOUNDATION,
104 .srccard = ((col_len > 1) ? fcs_col_get_card(col, col_len - 2)
105 : fc_solve_empty_card),
106 .destcard = fc_solve_empty_card,
107 .pri = 0};
108 // If it's an automove, just do it.
109 if (good_automove(soft_thread, o, fcs_card_rank(card)))
110 {
111 *a = true;
112 if (NUM_MOVES != 1)
113 {
114 soft_thread->possible_moves[0] = move_ptr[-1];
115 }
116 return 1;
117 }
118 }
119 }
120
121 #if MAX_NUM_FREECELLS > 0
122 /* Check for moves from soft_thread->current_pos.freecells to
123 * soft_thread->current_pos.foundations. */
124
125 for (int t = 0; t < LOCAL_FREECELLS_NUM; t++)
126 {
127 const fcs_card card = fcs_freecell_card(soft_thread->current_pos.s, t);
128 if (fcs_card_is_empty(card))
129 {
130 continue;
131 }
132 const int o = fcs_card_suit(card);
133 if (fcs_card_rank(card) ==
134 fcs_foundation_value(soft_thread->current_pos.s, o) + 1)
135 {
136 *(move_ptr++) = (typeof(*move_ptr)){.card = card,
137 .from = (unsigned char)t,
138 .fromtype = FCS_PATS__TYPE_FREECELL,
139 .to = (unsigned char)o,
140 .totype = FCS_PATS__TYPE_FOUNDATION,
141 .srccard = fc_solve_empty_card,
142 .destcard = fc_solve_empty_card,
143 .pri = 0};
144 // If it's an automove, just do it.
145 if (good_automove(soft_thread, o, fcs_card_rank(card)))
146 {
147 *a = true;
148 if (NUM_MOVES != 1)
149 {
150 soft_thread->possible_moves[0] = move_ptr[-1];
151 }
152 return 1;
153 }
154 }
155 }
156 #endif
157
158 // No more automoves, but remember if there were any moves out.
159 *a = false;
160 *num_cards_out = (int)NUM_MOVES;
161
162 /* Check for moves from non-singleton soft_thread->current_pos.stacks cells
163 to one of any
164 empty soft_thread->current_pos.stacks cells. */
165
166 const bool not_King_only =
167 #ifndef FCS_FREECELL_ONLY
168 (!((INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY)));
169 #else
170 true;
171 #endif
172
173 const int empty_col_idx = calc_empty_col_idx(soft_thread, LOCAL_STACKS_NUM);
174 const bool has_empty_col = (empty_col_idx >= 0);
175 if (has_empty_col)
176 {
177 for (int i = 0; i < LOCAL_STACKS_NUM; i++)
178 {
179 const_AUTO(i_col, fcs_state_get_col(soft_thread->current_pos.s, i));
180 const int i_col_len = fcs_col_len(i_col);
181 if (i_col_len > 1)
182 {
183 const fcs_card card = fcs_col_get_card(i_col, i_col_len - 1);
184 if (fcs_pats_is_king_only(not_King_only, card))
185 {
186 *(move_ptr++) = (typeof(*move_ptr)){
187 .card = card,
188 .from = (unsigned char)i,
189 .fromtype = FCS_PATS__TYPE_WASTE,
190 .to = (unsigned char)empty_col_idx,
191 .totype = FCS_PATS__TYPE_WASTE,
192 .srccard = fcs_col_get_card(i_col, i_col_len - 2),
193 .destcard = fc_solve_empty_card,
194 .pri = (signed char)soft_thread->pats_solve_params.x[3],
195 };
196 }
197 }
198 }
199 }
200
201 #ifndef FCS_FREECELL_ONLY
202 const fcs_card game_variant_suit_mask = instance->game_variant_suit_mask;
203 const fcs_card game_variant_desired_suit_value =
204 instance->game_variant_desired_suit_value;
205 #endif
206 /* Check for moves from soft_thread->current_pos.stacks to non-empty
207 * soft_thread->current_pos.stacks cells. */
208 for (int i = 0; i < LOCAL_STACKS_NUM; i++)
209 {
210 const_AUTO(i_col, fcs_state_get_col(soft_thread->current_pos.s, i));
211 const int i_col_len = fcs_col_len(i_col);
212 if (i_col_len > 0)
213 {
214 const fcs_card card = fcs_col_get_card(i_col, i_col_len - 1);
215 for (int w = 0; w < LOCAL_STACKS_NUM; w++)
216 {
217 if (i == w)
218 {
219 continue;
220 }
221 const_AUTO(
222 w_col, fcs_state_get_col(soft_thread->current_pos.s, w));
223 if (fcs_col_len(w_col) > 0)
224 {
225 const fcs_card w_card =
226 fcs_col_get_card(w_col, fcs_col_len(w_col) - 1);
227 if (fcs_card_rank(card) == fcs_card_rank(w_card) - 1 &&
228 fcs_pats_is_suitable(card, w_card
229 #ifndef FCS_FREECELL_ONLY
230 ,
231 game_variant_suit_mask,
232 game_variant_desired_suit_value
233 #endif
234 ))
235 {
236 *(move_ptr++) = (typeof(*move_ptr)){.card = card,
237 .from = (unsigned char)i,
238 .fromtype = FCS_PATS__TYPE_WASTE,
239 .to = (unsigned char)w,
240 .totype = FCS_PATS__TYPE_WASTE,
241 .srccard =
242 ((i_col_len > 1)
243 ? fcs_col_get_card(i_col, i_col_len - 2)
244 : fc_solve_empty_card),
245 .destcard = w_card,
246 .pri = (signed char)
247 soft_thread->pats_solve_params.x[4]};
248 }
249 }
250 }
251 }
252 }
253
254 #if MAX_NUM_FREECELLS > 0
255 /* Check for moves from soft_thread->current_pos.freecells to non-empty
256 * soft_thread->current_pos.stacks cells. */
257 for (int t = 0; t < LOCAL_FREECELLS_NUM; t++)
258 {
259 const fcs_card card = fcs_freecell_card(soft_thread->current_pos.s, t);
260 if (fcs_card_is_empty(card))
261 {
262 continue;
263 }
264
265 for (int w = 0; w < LOCAL_STACKS_NUM; w++)
266 {
267 var_AUTO(w_col, fcs_state_get_col(soft_thread->current_pos.s, w));
268 if (fcs_col_len(w_col) > 0)
269 {
270 const fcs_card w_card =
271 fcs_col_get_card(w_col, fcs_col_len(w_col) - 1);
272 if ((fcs_card_rank(card) == fcs_card_rank(w_card) - 1 &&
273 fcs_pats_is_suitable(card, w_card
274 #ifndef FCS_FREECELL_ONLY
275 ,
276 game_variant_suit_mask,
277 game_variant_desired_suit_value
278 #endif
279 )))
280 {
281 *(move_ptr++) = (typeof(*move_ptr)){.card = card,
282 .from = (unsigned char)t,
283 .fromtype = FCS_PATS__TYPE_FREECELL,
284 .to = (unsigned char)w,
285 .totype = FCS_PATS__TYPE_WASTE,
286 .srccard = fc_solve_empty_card,
287 .destcard = w_card,
288 .pri =
289 (signed char)soft_thread->pats_solve_params.x[5]};
290 }
291 }
292 }
293 }
294 #endif
295
296 #if MAX_NUM_FREECELLS > 0
297 /* Check for moves from soft_thread->current_pos.freecells to one of any
298 * empty soft_thread->current_pos.stacks cells. */
299 if (has_empty_col)
300 {
301 for (int t = 0; t < LOCAL_FREECELLS_NUM; t++)
302 {
303 const fcs_card card =
304 fcs_freecell_card(soft_thread->current_pos.s, t);
305 if (fcs_card_is_valid(card) &&
306 fcs_pats_is_king_only(not_King_only, card))
307 {
308 *(move_ptr++) = (typeof(*move_ptr)){.card = card,
309 .from = (unsigned char)t,
310 .fromtype = FCS_PATS__TYPE_FREECELL,
311 .to = (unsigned char)empty_col_idx,
312 .totype = FCS_PATS__TYPE_WASTE,
313 .srccard = fc_solve_empty_card,
314 .destcard = fc_solve_empty_card,
315 .pri = (signed char)soft_thread->pats_solve_params.x[6]};
316 }
317 }
318 }
319
320 /* Check for moves from soft_thread->current_pos.stacks to one of any empty
321 * soft_thread->current_pos.freecells cells. */
322 for (int t = 0; t < LOCAL_FREECELLS_NUM; t++)
323 {
324 if (!fcs_freecell_is_empty(soft_thread->current_pos.s, t))
325 {
326 continue;
327 }
328 for (int w = 0; w < LOCAL_STACKS_NUM; w++)
329 {
330 var_AUTO(w_col, fcs_state_get_col(soft_thread->current_pos.s, w));
331 const unsigned w_col_len = fcs_col_len(w_col);
332 if (w_col_len > 0)
333 {
334 *(move_ptr++) = (typeof(*move_ptr)){
335 .card = fcs_col_get_card(w_col, w_col_len - 1),
336 .from = (unsigned char)w,
337 .fromtype = FCS_PATS__TYPE_WASTE,
338 .to = (unsigned char)t,
339 .totype = FCS_PATS__TYPE_FREECELL,
340 .srccard = ((w_col_len > 1)
341 ? fcs_col_get_card(w_col, w_col_len - 2)
342 : fc_solve_empty_card),
343 .destcard = fc_solve_empty_card,
344 .pri = (signed char)soft_thread->pats_solve_params.x[7],
345 };
346 }
347 }
348 break;
349 }
350 #endif
351 return (int)NUM_MOVES;
352 }
353
354 /* Moves that can't be undone get slightly higher priority, since it means
355 we are moving a card for the first time. */
356
is_irreversible_move(const fcs_card game_variant_suit_mask,const fcs_card game_variant_desired_suit_value,const bool King_only,const fcs_pats__move * const move_ptr)357 static inline bool is_irreversible_move(
358 #ifndef FCS_FREECELL_ONLY
359 const fcs_card game_variant_suit_mask,
360 const fcs_card game_variant_desired_suit_value,
361 #endif
362 const bool King_only, const fcs_pats__move *const move_ptr)
363 {
364 if (move_ptr->totype == FCS_PATS__TYPE_FOUNDATION)
365 {
366 return true;
367 }
368 else if (move_ptr->fromtype == FCS_PATS__TYPE_WASTE)
369 {
370 const_SLOT(srccard, move_ptr);
371 if (fcs_card_is_valid(srccard))
372 {
373 const_SLOT(card, move_ptr);
374 if ((fcs_card_rank(card) != fcs_card_rank(srccard) - 1) ||
375 !fcs_pats_is_suitable(card, srccard
376 #ifndef FCS_FREECELL_ONLY
377 ,
378 game_variant_suit_mask, game_variant_desired_suit_value
379 #endif
380 ))
381 {
382 return true;
383 }
384 }
385 /* TODO : This is probably a bug because move_ptr->card probably cannot
386 * be
387 * FCS_PATS__KING - only FCS_PATS__KING bitwise-ORed with some other
388 * value.
389 * */
390 else if (King_only &&
391 move_ptr->card != fcs_make_card(FCS_PATS__KING, 0))
392 {
393 return true;
394 }
395 }
396
397 return false;
398 }
399
mark_irreversible(fcs_pats_thread * const soft_thread,const int n)400 static inline void mark_irreversible(
401 fcs_pats_thread *const soft_thread, const int n)
402 {
403 #ifndef FCS_FREECELL_ONLY
404 const fcs_instance *const instance = soft_thread->instance;
405
406 const fcs_card game_variant_suit_mask = instance->game_variant_suit_mask;
407 const fcs_card game_variant_desired_suit_value =
408 instance->game_variant_desired_suit_value;
409 #endif
410 const bool King_only =
411 #ifndef FCS_FREECELL_ONLY
412 (INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY);
413 #else
414 false;
415 #endif
416 const_AUTO(x_param_8, soft_thread->pats_solve_params.x[8]);
417
418 fcs_pats__move *move_ptr = soft_thread->possible_moves;
419 const fcs_pats__move *const moves_end = move_ptr + n;
420 for (; move_ptr < moves_end; ++move_ptr)
421 {
422 if (is_irreversible_move(
423 #ifndef FCS_FREECELL_ONLY
424 game_variant_suit_mask, game_variant_desired_suit_value,
425 #endif
426 King_only, move_ptr))
427 {
428 move_ptr->pri += x_param_8;
429 }
430 }
431 }
432
433 /* For each pile, return a unique identifier. Although there are a
434 large number of possible piles, generally fewer than 1000 different
435 piles appear in any given game. We'll use the pile's hash to find
436 a hash bucket that contains a short list of piles, along with their
437 identifiers. */
438
get_pilenum(fcs_pats_thread * const soft_thread,const int w)439 static inline int get_pilenum(fcs_pats_thread *const soft_thread, const int w)
440 {
441 /* For a given pile, get its unique pile id. If it doesn't have
442 one, add it to the appropriate list and give it one. First, get
443 the hash bucket. */
444 const int bucket =
445 soft_thread->current_pos.stack_hashes[w] % FC_SOLVE_BUCKETLIST_NBUCKETS;
446
447 // Look for the pile in this bucket.
448 fcs_pats__bucket_list *list_iter = soft_thread->buckets_list[bucket],
449 *last_item = NULL;
450 const_AUTO(w_col, fcs_state_get_col(soft_thread->current_pos.s, w));
451 const uint_fast16_t w_col_len = fcs_col_len(w_col);
452 const char *w_col_data = (const char *)w_col + 1;
453 for (; list_iter; list_iter = list_iter->next)
454 {
455 if (list_iter->hash == soft_thread->current_pos.stack_hashes[w])
456 {
457 if (memcmp((const char *)list_iter->pile, w_col_data, w_col_len) ==
458 0)
459 {
460 break;
461 }
462 }
463 last_item = list_iter;
464 }
465
466 // If we didn't find it, make a new one and add it to the list.
467 if (!list_iter)
468 {
469 if (soft_thread->next_pile_idx == FC_SOLVE__MAX_NUM_PILES)
470 {
471 #if 0
472 fc_solve_msg("Ran out of pile numbers!");
473 #endif
474 return -1;
475 }
476 list_iter = fc_solve_pats__new(soft_thread, fcs_pats__bucket_list);
477 if (list_iter == NULL)
478 {
479 return -1;
480 }
481 list_iter->pile =
482 fc_solve_pats__new_array(soft_thread, unsigned char, w_col_len + 1);
483 if (list_iter->pile == NULL)
484 {
485 fc_solve_pats__free_ptr(
486 soft_thread, list_iter, fcs_pats__bucket_list);
487 return -1;
488 }
489
490 /* Store the new pile along with its hash. Maintain
491 a reverse mapping so we can unpack the piles swiftly. */
492 memcpy((char *)list_iter->pile, w_col_data, w_col_len + 1);
493 list_iter->hash = soft_thread->current_pos.stack_hashes[w];
494 list_iter->next = NULL;
495 if (last_item)
496 {
497 last_item->next = list_iter;
498 }
499 else
500 {
501 soft_thread->buckets_list[bucket] = list_iter;
502 }
503 soft_thread->bucket_from_pile_lookup[list_iter->pilenum =
504 soft_thread->next_pile_idx++] =
505 list_iter;
506 }
507
508 return list_iter->pilenum;
509 }
510
win(fcs_pats_thread * const soft_thread,fcs_pats_position * const pos)511 static inline void win(
512 fcs_pats_thread *const soft_thread, fcs_pats_position *const pos)
513 {
514 if (soft_thread->moves_to_win)
515 {
516 free(soft_thread->moves_to_win);
517 soft_thread->moves_to_win = NULL;
518 }
519
520 size_t num_moves = 0;
521 for (fcs_pats_position *p = pos; p->parent; p = p->parent)
522 {
523 ++num_moves;
524 }
525 typeof(soft_thread->moves_to_win) moves_to_win =
526 SMALLOC(moves_to_win, num_moves);
527 if (!moves_to_win)
528 {
529 return; // how sad, so close...
530 }
531 var_AUTO(moves_ptr, moves_to_win + num_moves);
532 for (fcs_pats_position *p = pos; p->parent; p = p->parent)
533 {
534 *(--moves_ptr) = (p->move);
535 }
536
537 soft_thread->moves_to_win = moves_to_win;
538 soft_thread->num_moves_to_win = num_moves;
539 }
540
541 #ifndef FCS_FREECELL_ONLY
542 /* This prune applies only to Seahaven in -k mode: if we're putting a card
543 onto a soft_thread->current_pos.stacks pile, and if that pile already has
544 LOCAL_FREECELLS_NUM+1 cards of this suit in
545 a row, and if there is a smaller card of the same suit below the run, then
546 the position is unsolvable. This cuts out a lot of useless searching, so
547 it's worth checking. */
548
prune_seahaven(fcs_pats_thread * const soft_thread,const fcs_pats__move * const move_ptr)549 static inline int prune_seahaven(
550 fcs_pats_thread *const soft_thread, const fcs_pats__move *const move_ptr)
551 {
552 const fcs_instance *const instance = soft_thread->instance;
553 const_SLOT(game_params, instance);
554
555 if (!(GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) ==
556 FCS_SEQ_BUILT_BY_SUIT) ||
557 !(INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY) ||
558 move_ptr->totype != FCS_PATS__TYPE_WASTE)
559 {
560 return false;
561 }
562
563 // Count the number of cards below this card.
564 const fcs_card r = fcs_card_rank(move_ptr->card) + 1;
565 const fcs_card s = fcs_card_suit(move_ptr->card);
566 const_AUTO(
567 col, fcs_state_get_col(soft_thread->current_pos.s, move_ptr->to));
568 const int len = fcs_col_len(col);
569
570 {
571 int j = 0;
572 for (int i = len - 1; i >= 0; i--)
573 {
574 const_AUTO(card, fcs_col_get_card(col, i));
575 if (fcs_card_suit(card) == s && fcs_card_rank(card) == r + j)
576 {
577 j++;
578 }
579 }
580 if (j < LOCAL_FREECELLS_NUM + 1)
581 {
582 return false;
583 }
584 }
585
586 // If there's a smaller card of this suit in the pile, we can prune
587 // the move.
588 const int r_minus = r - 1;
589 for (int i = 0; i < len; i++)
590 {
591 const_AUTO(card, fcs_col_get_card(col, i));
592 if ((fcs_card_suit(card) == s) && (fcs_card_rank(card) < r_minus))
593 {
594 return true;
595 }
596 }
597
598 return false;
599 }
600 #endif
601
602 /* This utility routine is used to check if a card is ever moved in
603 a sequence of moves. */
604
was_card_moved(const fcs_card card,fcs_pats__move * const * const moves,const int j)605 static inline int was_card_moved(
606 const fcs_card card, fcs_pats__move *const *const moves, const int j)
607 {
608 for (int i = 0; i < j; i++)
609 {
610 if (moves[i]->card == card)
611 {
612 return true;
613 }
614 }
615 return false;
616 }
617
618 /* This utility routine is used to check if a card is ever used as a
619 destination in a sequence of moves. */
is_card_dest(const fcs_card card,fcs_pats__move * const * const moves,const int j)620 static inline int is_card_dest(
621 const fcs_card card, fcs_pats__move *const *const moves, const int j)
622 {
623 for (int i = 0; i < j; i++)
624 {
625 if (moves[i]->destcard == card)
626 {
627 return true;
628 }
629 }
630 return false;
631 }
632
was_card_moved_or_dest(const fcs_card card,fcs_pats__move * const * const moves,const int j)633 static inline int was_card_moved_or_dest(
634 const fcs_card card, fcs_pats__move *const *const moves, const int j)
635 {
636 return (was_card_moved(card, moves, j) || is_card_dest(card, moves, j));
637 }
638
639 // Prune redundant moves, if we can prove that they really are redundant.
640 #define MAX_PREVIOUS_MOVES 4 /* Increasing this beyond 4 doesn't do much. */
641
prune_redundant(fcs_pats_thread * const soft_thread GCC_UNUSED,const fcs_pats__move * const move_ptr,fcs_pats_position * const pos0)642 static inline int prune_redundant(fcs_pats_thread *const soft_thread GCC_UNUSED,
643 const fcs_pats__move *const move_ptr, fcs_pats_position *const pos0)
644 {
645 DECLARE_STACKS();
646 int j;
647 fcs_pats__move *m, *prev[MAX_PREVIOUS_MOVES];
648
649 // Don't move the same card twice in a row.
650 fcs_pats_position *pos = pos0;
651 if (pos->depth == 0)
652 {
653 return false;
654 }
655 m = &pos->move;
656 if (m->card == move_ptr->card)
657 {
658 return true;
659 }
660
661 /* The check above is the simplest case of the more general strategy
662 of searching the move stack (up the chain of parents) to prove that
663 the current move is redundant. To do that, we need some more data. */
664
665 pos = pos->parent;
666 if (pos->depth == 0)
667 {
668 return false;
669 }
670 prev[0] = m;
671 j = -1;
672 for (int i = 1; i < MAX_PREVIOUS_MOVES; i++)
673 {
674 // Make a list of the last few moves.
675 prev[i] = &pos->move;
676
677 // Locate the last time we moved this card.
678 if (m->card == move_ptr->card)
679 {
680 j = i;
681 break;
682 }
683
684 /* Keep going up the move stack, looking for this
685 card, until we run out of moves (or patience). */
686 pos = pos->parent;
687 if (pos->depth == 0)
688 {
689 return false;
690 }
691 }
692
693 /* If we haven't moved this card recently, assume this isn't
694 a redundant move. */
695 if (j < 0)
696 {
697 return false;
698 }
699
700 /* If the number of empty soft_thread->current_pos.freecells cells
701 * ever goes to zero, from prev[0] to
702 prev[j-1], there may be a dependency. We also want to know if there
703 were any empty soft_thread->current_pos.freecells cells on move prev[j]. */
704 bool was_all_freecells_occupied = false;
705 pos = pos0;
706 for (int i = 0; i < j; i++)
707 {
708 was_all_freecells_occupied =
709 was_all_freecells_occupied ||
710 (pos->num_cards_in_freecells == LOCAL_FREECELLS_NUM);
711 pos = pos->parent;
712 }
713
714 /* Now, prev[j] (m) is a move involving the same card as the current
715 move. See if the current move inverts that move. There are several
716 cases. */
717
718 /* soft_thread->current_pos.freecells -> soft_thread->current_pos.stacks,
719 * ..., soft_thread->current_pos.stacks ->
720 * soft_thread->current_pos.freecells */
721 if (m->fromtype == FCS_PATS__TYPE_FREECELL &&
722 m->totype == FCS_PATS__TYPE_WASTE &&
723 move_ptr->fromtype == FCS_PATS__TYPE_WASTE &&
724 move_ptr->totype == FCS_PATS__TYPE_FREECELL)
725 {
726 /* If the number of soft_thread->current_pos.freecells cells goes to
727 zero, we have a soft_thread->current_pos.freecells
728 dependency, and we can't prune. */
729 if (was_all_freecells_occupied)
730 {
731 return false;
732 }
733
734 /* If any intervening move used this card as a destination,
735 we have a dependency and we can't prune. */
736 if (is_card_dest(move_ptr->card, prev, j))
737 {
738 return false;
739 }
740 return true;
741 }
742
743 /* soft_thread->current_pos.stacks -> soft_thread->current_pos.freecells,
744 * ..., soft_thread->current_pos.freecells ->
745 * soft_thread->current_pos.stacks */
746 /* soft_thread->current_pos.stacks -> soft_thread->current_pos.stacks, ...,
747 * soft_thread->current_pos.stacks -> soft_thread->current_pos.stacks */
748
749 if ((m->fromtype == FCS_PATS__TYPE_WASTE &&
750 m->totype == FCS_PATS__TYPE_FREECELL &&
751 move_ptr->fromtype == FCS_PATS__TYPE_FREECELL &&
752 move_ptr->totype == FCS_PATS__TYPE_WASTE) ||
753 (m->fromtype == FCS_PATS__TYPE_WASTE &&
754 m->totype == FCS_PATS__TYPE_WASTE &&
755 move_ptr->fromtype == FCS_PATS__TYPE_WASTE &&
756 move_ptr->totype == FCS_PATS__TYPE_WASTE))
757 {
758 /* If we're not putting the card back where we found it,
759 it's not an inverse. */
760 if (m->srccard != move_ptr->destcard)
761 {
762 return false;
763 }
764
765 /* If any of the intervening moves either moves the card
766 that was uncovered or uses it as a destination (including
767 fc_solve_empty_card), there is a dependency. */
768 if (was_card_moved_or_dest(move_ptr->destcard, prev, j))
769 {
770 return false;
771 }
772
773 return true;
774 }
775
776 /* These are not inverse prunes, we're taking a shortcut. */
777
778 /* soft_thread->current_pos.stacks -> soft_thread->current_pos.stacks, ...,
779 * soft_thread->current_pos.stacks -> soft_thread->current_pos.freecells */
780 if (m->fromtype == FCS_PATS__TYPE_WASTE &&
781 m->totype == FCS_PATS__TYPE_WASTE &&
782 move_ptr->fromtype == FCS_PATS__TYPE_WASTE &&
783 move_ptr->totype == FCS_PATS__TYPE_FREECELL)
784 {
785 /* If we could have moved the card to soft_thread->current_pos.freecells
786 on the
787 first move, prune. There are other cases, but they
788 are more complicated. */
789 if (pos->num_cards_in_freecells != LOCAL_FREECELLS_NUM &&
790 !was_all_freecells_occupied)
791 {
792 return true;
793 }
794
795 return false;
796 }
797
798 /* soft_thread->current_pos.freecells -> soft_thread->current_pos.stacks,
799 * ..., soft_thread->current_pos.stacks -> soft_thread->current_pos.stacks
800 */
801
802 if (m->fromtype == FCS_PATS__TYPE_FREECELL &&
803 m->totype == FCS_PATS__TYPE_WASTE &&
804 move_ptr->fromtype == FCS_PATS__TYPE_WASTE &&
805 move_ptr->totype == FCS_PATS__TYPE_WASTE)
806 {
807 /* We can prune these moves as long as the intervening
808 moves don't touch move_ptr->destcard. */
809
810 if (was_card_moved_or_dest(move_ptr->destcard, prev, j))
811 {
812 return false;
813 }
814
815 return true;
816 }
817
818 return false;
819 }
820
821 // Next card in rank.
fcs_pats_next_card(const fcs_card card)822 static inline fcs_card fcs_pats_next_card(const fcs_card card)
823 {
824 return card + (1 << 2);
825 }
826
827 /* Move prioritization. Given legal, pruned moves, there are still some
828 that are a waste of time, especially in the endgame where there are lots of
829 possible moves, but few productive ones. Note that we also prioritize
830 positions when they are added to the queue. */
831
prioritize(fcs_pats_thread * const soft_thread,fcs_pats__move * const moves_start,const int n)832 static inline void prioritize(fcs_pats_thread *const soft_thread,
833 fcs_pats__move *const moves_start, const int n)
834 {
835 DECLARE_STACKS();
836 int pile[8];
837
838 /* There are 4 cards that we "need": the next cards to go out. We
839 give higher priority to the moves that remove cards from the piles
840 containing these cards. */
841 for (size_t i = 0; i < COUNT(pile); i++)
842 {
843 pile[i] = -1;
844 }
845
846 fcs_card needed_cards[FCS_NUM_SUITS];
847 for (fcs_card suit = 0; suit < FCS_NUM_SUITS; suit++)
848 {
849 needed_cards[suit] = fc_solve_empty_card;
850 const fcs_card rank =
851 fcs_foundation_value(soft_thread->current_pos.s, suit);
852 if (rank != FCS_PATS__KING)
853 {
854 needed_cards[suit] = fcs_make_card(rank + 1, suit);
855 }
856 }
857
858 /* Locate the needed cards. There's room for optimization here,
859 like maybe an array that keeps track of every card; if maintaining
860 such an array is not too expensive. */
861 int num_piles = 0;
862
863 for (int w = 0; w < LOCAL_STACKS_NUM; w++)
864 {
865 var_AUTO(col, fcs_state_get_col(soft_thread->current_pos.s, w));
866 const int len = fcs_col_len(col);
867 for (int i = 0; i < len; i++)
868 {
869 const fcs_card card = fcs_col_get_card(col, i);
870 const int suit = fcs_card_suit(card);
871
872 /* Save the locations of the piles containing
873 not only the card we need next, but the card
874 after that as well. */
875 if (fcs_card_is_valid(needed_cards[suit]) &&
876 (card == needed_cards[suit] ||
877 card == fcs_pats_next_card(needed_cards[suit])))
878 {
879 pile[num_piles++] = w;
880 if (num_piles == COUNT(pile))
881 {
882 goto end_of_stacks;
883 }
884 }
885 }
886 }
887
888 end_of_stacks:;
889 /* Now if any of the moves remove a card from any of the piles
890 listed in pile[], bump their priority. Likewise, if a move
891 covers a card we need, decrease its priority. These priority
892 increments and decrements were determined empirically. */
893 const_AUTO(moves_end, moves_start + n);
894 for (fcs_pats__move *move_ptr = moves_start; move_ptr < moves_end;
895 move_ptr++)
896 {
897 if (fcs_card_is_empty(move_ptr->card))
898 {
899 continue;
900 }
901 if (move_ptr->fromtype == FCS_PATS__TYPE_WASTE)
902 {
903 const int w = move_ptr->from;
904 for (int j = 0; j < num_piles; j++)
905 {
906 if (w == pile[j])
907 {
908 move_ptr->pri += soft_thread->pats_solve_params.x[0];
909 }
910 }
911 var_AUTO(col, fcs_state_get_col(soft_thread->current_pos.s, w));
912 if (fcs_col_len(col) > 1)
913 {
914 const fcs_card card =
915 fcs_col_get_card(col, fcs_col_len(col) - 2);
916 if (card == needed_cards[(int)fcs_card_suit(card)])
917 {
918 move_ptr->pri += soft_thread->pats_solve_params.x[1];
919 }
920 }
921 }
922 if (move_ptr->totype == FCS_PATS__TYPE_WASTE)
923 {
924 for (int j = 0; j < num_piles; j++)
925 {
926 if (move_ptr->to == pile[j])
927 {
928 move_ptr->pri -= soft_thread->pats_solve_params.x[2];
929 }
930 }
931 }
932 }
933 }
934
is_win(fcs_pats_thread * const soft_thread)935 static inline bool is_win(fcs_pats_thread *const soft_thread)
936 {
937 for (int o = 0; o < 4; o++)
938 {
939 if (fcs_foundation_value(soft_thread->current_pos.s, o) !=
940 FCS_PATS__KING)
941 {
942 return false;
943 }
944 }
945 return true;
946 }
947
948 // Generate an array of the moves we can make from this position.
fc_solve_pats__get_moves(fcs_pats_thread * const soft_thread,fcs_pats_position * const pos,int * const num_moves)949 fcs_pats__move *fc_solve_pats__get_moves(fcs_pats_thread *const soft_thread,
950 fcs_pats_position *const pos, int *const num_moves)
951 {
952 int num_cards_out = 0;
953
954 // Fill in the soft_thread->possible_moves array.
955 bool a;
956 const int total_num_moves =
957 get_possible_moves(soft_thread, &a, &num_cards_out);
958 int n = total_num_moves;
959
960 if (!a)
961 {
962 // Throw out some obviously bad (non-auto)moves.
963 var_PTR(move_ptr, soft_thread->possible_moves);
964 const_AUTO(moves_end, move_ptr + total_num_moves);
965 for (; move_ptr < moves_end; move_ptr++)
966 {
967 #ifndef FCS_FREECELL_ONLY
968 // Special prune for Seahaven -k.
969 if (prune_seahaven(soft_thread, move_ptr))
970 {
971 move_ptr->card = fc_solve_empty_card;
972 n--;
973 continue;
974 }
975 #endif
976
977 // Prune redundant moves.
978 if (prune_redundant(soft_thread, move_ptr, pos))
979 {
980 move_ptr->card = fc_solve_empty_card;
981 n--;
982 }
983 }
984
985 mark_irreversible(soft_thread, n);
986 }
987
988 // No moves? Maybe we won.
989 if (n == 0)
990 {
991 if (is_win(soft_thread))
992 {
993 // Report the win.
994 win(soft_thread, pos);
995
996 if (soft_thread->dont_exit_on_sol)
997 {
998 soft_thread->num_solutions++;
999 }
1000 else
1001 {
1002 soft_thread->status = FCS_PATS__WIN;
1003 }
1004 }
1005
1006 // We lost.
1007 return NULL;
1008 }
1009
1010 /* Prioritize these moves. Automoves don't get queued, so they
1011 don't need a priority. */
1012 if (!a)
1013 {
1014 prioritize(soft_thread, soft_thread->possible_moves, total_num_moves);
1015 }
1016
1017 /* Now copy to safe storage and return. Non-auto moves out get put
1018 at the end. Queueing them isn't a good idea because they are still
1019 good moves, even if they didn't pass the automove test. So we still
1020 do the recursive solve() on them, but only after queueing the other
1021 moves. */
1022 fcs_pats__move *move_ptr, *moves_start;
1023 move_ptr = moves_start =
1024 fc_solve_pats__new_array(soft_thread, fcs_pats__move, (size_t)n);
1025 if (move_ptr == NULL)
1026 {
1027 return NULL;
1028 }
1029 *num_moves = n;
1030 if (a || num_cards_out == 0)
1031 {
1032 for (int i = 0; i < total_num_moves; i++)
1033 {
1034 if (fcs_card_is_valid(soft_thread->possible_moves[i].card))
1035 {
1036 *(move_ptr++) =
1037 soft_thread->possible_moves[i]; /* struct copy */
1038 }
1039 }
1040 }
1041 else
1042 {
1043 for (int i = num_cards_out; i < total_num_moves; i++)
1044 {
1045 if (fcs_card_is_valid(soft_thread->possible_moves[i].card))
1046 {
1047 *(move_ptr++) =
1048 soft_thread->possible_moves[i]; /* struct copy */
1049 }
1050 }
1051 for (int i = 0; i < num_cards_out; i++)
1052 {
1053 if (fcs_card_is_valid(soft_thread->possible_moves[i].card))
1054 {
1055 *(move_ptr++) = soft_thread->possible_moves[i];
1056 }
1057 }
1058 }
1059
1060 return moves_start;
1061 }
1062
1063 // Comparison function for sorting the soft_thread->current_pos.stacks piles.
wcmp(fcs_pats_thread * const soft_thread,const int a,const int b)1064 static inline int wcmp(
1065 fcs_pats_thread *const soft_thread, const int a, const int b)
1066 {
1067 if (soft_thread->pats_solve_params.x[9] < 0)
1068 {
1069 return soft_thread->current_pos.stack_ids[b] -
1070 soft_thread->current_pos.stack_ids[a]; // newer piles first
1071 }
1072 else
1073 {
1074 return soft_thread->current_pos.stack_ids[a] -
1075 soft_thread->current_pos.stack_ids[b]; // older piles first
1076 }
1077 }
1078
1079 /* Sort the piles, to remove the physical differences between logically
1080 equivalent layouts. Assume it's already mostly sorted. */
fc_solve_pats__sort_piles(fcs_pats_thread * const soft_thread)1081 void fc_solve_pats__sort_piles(fcs_pats_thread *const soft_thread)
1082 {
1083 DECLARE_STACKS();
1084 // Make sure all the piles have id numbers.
1085 for (int stack_idx = 0; stack_idx < LOCAL_STACKS_NUM; stack_idx++)
1086 {
1087 if ((soft_thread->current_pos.stack_ids[stack_idx] < 0) &&
1088 ((soft_thread->current_pos.stack_ids[stack_idx] =
1089 get_pilenum(soft_thread, stack_idx)) < 0))
1090 {
1091 return;
1092 }
1093 }
1094
1095 // Sort them.
1096 soft_thread->current_pos.column_idxs[0] = 0;
1097 for (int i = 1; i < LOCAL_STACKS_NUM; i++)
1098 {
1099 const_AUTO(w, i - 1);
1100 if (wcmp(soft_thread, soft_thread->current_pos.column_idxs[w], i) < 0)
1101 {
1102 soft_thread->current_pos.column_idxs[i] = i;
1103 }
1104 else
1105 {
1106 for (int j = w; j >= 0; --j)
1107 {
1108 soft_thread->current_pos.column_idxs[j + 1] =
1109 soft_thread->current_pos.column_idxs[j];
1110 if (j == 0 ||
1111 wcmp(soft_thread,
1112 soft_thread->current_pos.column_idxs[j - 1], i) < 0)
1113 {
1114 soft_thread->current_pos.column_idxs[j] = i;
1115 break;
1116 }
1117 }
1118 }
1119 }
1120 }
1121