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