home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / share / sol-games / freecell.scm < prev    next >
Encoding:
Text File  |  2006-08-22  |  34.3 KB  |  1,016 lines

  1. ;;; freecell.scm -- Free Cell game for AisleRiot.
  2.  
  3. ;; Copyright (C) 1998, 2003 Changwoo Ryu
  4.  
  5. ;; Author: Changwoo Ryu <cwryu@adam.kaist.ac.kr>
  6.  
  7. ;; This program is free software; you can redistribute it and'or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation; either version 2, or (at your option)
  10. ;; any later version.
  11.  
  12. ;; This program is distributed in the hope that it will be useful,
  13. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. ;; GNU General Public License for more details.
  16.  
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with this program; if not, write to the Free Software
  19. ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20.  
  21. ;;; Commentary:
  22.  
  23. ;; FREECELL
  24. ;;
  25. ;; * The 4 slots in the left-top are called "freecells". (F? in the below)
  26. ;; * The 4 slots in the right-top are called "homecells". (H? in the below)
  27. ;; * The 8 slots in the bottom are called "fields". (D? in the below)
  28. ;;
  29. ;;  -------------------------------------------
  30. ;;  |                                         |
  31. ;;  |(0)  (1)  (2)  (3)    (4)  (5)  (6)  (7) |
  32. ;;  | F1   F2   F3   F4     H1   H2   H3   H4 |
  33. ;;  |                                         |
  34. ;;  |                                         |
  35. ;;  | (8)  (9)  (10) (11) (12) (13) (14) (15) |
  36. ;;  |  D1   D2   D3   D4   D5   D6   D7   D8  |
  37. ;;  |                                         |
  38. ;;  -------------------------------------------
  39.  
  40. ;;; Code:
  41.  
  42. ;;
  43. ;; Constants
  44. ;;
  45. (define freecell-1 0)
  46. (define freecell-2 1)
  47. (define freecell-3 2)
  48. (define freecell-4 3)
  49. (define homecell-1 4)
  50. (define homecell-2 5)
  51. (define homecell-3 6)
  52. (define homecell-4 7)
  53. (define field-1    8)
  54. (define field-2    9)
  55. (define field-3    10)
  56. (define field-4    11)
  57. (define field-5    12)
  58. (define field-6    13)
  59. (define field-7    14)
  60. (define field-8    15)
  61.  
  62. (define freecells (list freecell-1 freecell-2 freecell-3 freecell-4))
  63. (define homecells (list homecell-1 homecell-2 homecell-3 homecell-4))
  64. (define fields (list field-1 field-2 field-3 field-4
  65.                      field-5 field-6 field-7 field-8))
  66. (define half-fields (list field-1 field-2 field-3 field-4))
  67.  
  68. ;;
  69. ;; Initial cards
  70. ;;
  71. (define (deal-initial-setup)
  72.   (let ((fields (list field-1 field-2 field-3 field-4
  73.                    field-5 field-6 field-7 field-8))
  74.     (half-fields (list field-1 field-2 field-3 field-4)))
  75.     (deal-cards-face-up-from-deck DECK
  76.                   (append fields fields fields
  77.                       fields fields fields
  78.                       half-fields))))
  79.  
  80. ;;
  81. ;; Utilities
  82. ;;
  83.  
  84. (define (freecell? slot)
  85.   (and (>= slot freecell-1) (<= slot freecell-4)))
  86.  
  87. (define (homecell? slot)
  88.   (and (>= slot homecell-1) (<= slot homecell-4)))
  89.  
  90. (define (field? slot)
  91.   (and (>= slot field-1) (<= slot field-8)))
  92.  
  93. (define (slot-type slot)
  94.   (cond ((freecell? slot) 'freecell)
  95.     ((homecell? slot) 'homecell)
  96.     ((field? slot) 'field)))
  97.  
  98. (define (opposite-color color)
  99.   (if (eq? color red) black red))
  100.       
  101.  
  102. ;;
  103. ;; Utilities for the homecells
  104. ;;
  105.  
  106. ;; homecell id which holds the suit or an empty slot if there is no slot.
  107. (define (homecell-by-suit suit)
  108.   (define (p? slot)
  109.     (and (not (empty-slot? slot))
  110.      (= (get-suit (get-top-card slot)) suit)))
  111.   (cond ((p? homecell-1) homecell-1)
  112.     ((p? homecell-2) homecell-2)
  113.     ((p? homecell-3) homecell-3)
  114.     ((p? homecell-4) homecell-4)
  115.     (#t (any-empty-homecell))))
  116.  
  117. ;; An empty homecell's id, if any
  118. (define (any-empty-homecell)
  119.   (cond ((empty-slot? homecell-1) homecell-1)
  120.     ((empty-slot? homecell-2) homecell-2)
  121.     ((empty-slot? homecell-3) homecell-3)
  122.     ((empty-slot? homecell-4) homecell-4)
  123.     (else #f)))
  124.  
  125. (define (homecell-join? prev next)
  126.   (and (eq? (get-suit prev) (get-suit next))
  127.        (eq? (+ (get-value prev) 1) (get-value next))))
  128.  
  129. (define (get-color-homecells color)
  130.   (define (iter n l)
  131.     (if (< n homecell-1)
  132.     l
  133.     (if (eq? (get-top-card n) color)
  134.         (iter (- n 1) (cons n l))
  135.         (iter (- n 1) l))))
  136.   (iter homecell-4 '()))
  137.  
  138. ;;
  139. ;; Utilities for freecells
  140. ;;
  141.  
  142. ;; The total number of empty freecells
  143. (define (empty-freecell-number)
  144.   (do ((i freecell-1 (+ i 1))
  145.        (sum 0 (+ sum (if (empty-slot? i) 1 0))))
  146.       ((> i freecell-4) sum)))
  147.  
  148. ;; An empty freecell's id, if any
  149. (define (any-empty-freecell)
  150.   (cond ((empty-slot? freecell-1) freecell-1)
  151.     ((empty-slot? freecell-2) freecell-2)
  152.     ((empty-slot? freecell-3) freecell-3)
  153.     ((empty-slot? freecell-4) freecell-4)
  154.     (else #f)))
  155.  
  156. ;;
  157. ;; Utilities for fields
  158. ;;
  159.  
  160. (define (field-join? lower upper)
  161.   (and (not (eq? (get-color lower) (get-color upper)))
  162.        (eq? (+ (get-value lower) 1) (get-value upper))))
  163.  
  164. (define (field-sequence? card-list)
  165.   (or (null? card-list)
  166.       (null? (cdr card-list))
  167.       (and (field-join? (car card-list) (cadr card-list))
  168.        (field-sequence? (cdr card-list)))))
  169.  
  170. (define (empty-field-number)
  171.   (do ((i field-1 (+ i 1))
  172.        (sum 0 (+ sum (if (empty-slot? i) 1 0))))
  173.       ((> i field-8) sum)))
  174.  
  175. ;;
  176. ;; How to move cards
  177. ;;
  178.  
  179. (define (movable-to-homecell? card-list homecell-id)
  180.   (and (= (length card-list) 1)
  181.        (if (empty-slot? homecell-id)
  182.            (eq? (get-value (car card-list)) ace)
  183.            (homecell-join? (car (get-cards homecell-id)) (car card-list)))))
  184.  
  185. (define (move-to-homecell card-list homecell-id)
  186.     (and
  187.         (= (length card-list) 1)
  188.         (move-card-to-homecell (car card-list) homecell-id)
  189.     )
  190. )
  191.  
  192. (define (move-card-to-homecell card homecell-id)
  193.     (cond
  194.         ; if the homecell is empty, we can add an ace to it.
  195.         ((and
  196.             (empty-slot? homecell-id)
  197.             (eq? (get-value card) ace)
  198.             (add-to-score! 1)
  199.             (add-card! homecell-id card)
  200.             (update-auto (get-suit card) (get-value card)))
  201.         #t)
  202.         ; Put a +1 card into the homecell, whose suit is same.
  203.         ((and
  204.             (not (empty-slot? homecell-id))
  205.             (homecell-join? (car (get-cards homecell-id)) card)
  206.             (add-to-score! 1)
  207.             (add-card! homecell-id card)
  208.             (update-auto (get-suit card) (get-value card)))
  209.         #t)
  210.         (#t #f)
  211.     )
  212. )
  213.  
  214. ;; Version of move-to-field that only tests a move or supermove.
  215. (define (movable-to-field? start-slot card-list field-id)
  216.   (and (field-sequence? card-list)
  217.        (<= (length card-list)
  218.            (* (+ (empty-freecell-number) 1)
  219.               ($expt 2 (max (- (empty-field-number)
  220.                                (if (empty-slot? field-id) 1 0)
  221.                                (if (empty-slot? start-slot) 1 0))
  222.                             0))))
  223.        (or (empty-slot? field-id)
  224.            (let ((dest-top (car (get-cards field-id))))
  225.              (and (field-sequence? (append card-list (list dest-top))))))))
  226.  
  227.  
  228. (define (move-to-field start-slot card-list field-id)
  229.   (and (movable-to-field? start-slot card-list field-id)
  230.        (add-cards! field-id card-list)))
  231.  
  232. (define (movable-to-freecell? card-list freecell-id)
  233.   (and (= (length card-list) 1)
  234.        (empty-slot? freecell-id)))
  235.  
  236. (define (move-to-freecell card-list freecell-id)
  237.     (and
  238.         (= (length card-list) 1)
  239.         (move-card-to-freecell (car card-list) freecell-id)
  240.     )
  241. )
  242.  
  243. (define (move-card-to-freecell card freecell-id)
  244.     (and
  245.         (not (boolean? freecell-id))
  246.         (empty-slot? freecell-id)
  247.         (add-card! freecell-id card)
  248.     )
  249. )
  250.  
  251. ;;
  252. ;; Auto move stuffs
  253. ;;
  254.  
  255. (def-save-var highest-club 0)
  256. (def-save-var highest-diamond 0)
  257. (def-save-var highest-heart 0)
  258. (def-save-var highest-spade 0)
  259.  
  260. (define (update-auto suit value)
  261.     (cond
  262.         ((eq? suit club) (set! highest-club value))
  263.         ((eq? suit diamond) (set! highest-diamond value))
  264.         ((eq? suit heart) (set! highest-heart value))
  265.         ((eq? suit spade) (set! highest-spade value))
  266.     )
  267. )
  268.  
  269. (define (max-auto-red)
  270.     (min
  271.         (+ 2 (min highest-club highest-spade))
  272.         (+ 3 (min highest-diamond highest-heart))
  273.     )
  274. )
  275.  
  276. (define (max-auto-black)
  277.     (min
  278.         (+ 2 (min highest-diamond highest-heart))
  279.         (+ 3 (min highest-club highest-spade))
  280.     )
  281. )
  282.  
  283. (define (move-low-cards slot)
  284.   (or
  285.    (and
  286.     (not (homecell? slot))
  287.     (not (empty-slot? slot))
  288.     (let ((card (get-top-card slot)))
  289.       (if (= (get-color card) red)
  290.       (and
  291.        (<= (get-value card) (max-auto-red))
  292.        (move-card-to-homecell card (homecell-by-suit (get-suit card)))
  293.        (remove-card slot)
  294.        (delayed-call ((lambda (x) (lambda () (move-low-cards x))) 0))
  295.        )
  296.       (and
  297.        (<= (get-value card) (max-auto-black))
  298.        (move-card-to-homecell card (homecell-by-suit (get-suit card)))
  299.        (remove-card slot)
  300.        (delayed-call ((lambda (x) (lambda () (move-low-cards x))) 0))
  301.                     ;    (move-low-cards 0)
  302.        )
  303.       )
  304.       )
  305.     )
  306.    (if (< slot field-8)
  307.        (move-low-cards (+ 1 slot))
  308.        #t
  309.        )
  310.    )
  311.   )
  312.  
  313. ;;
  314. ;; Callbacks & Initialize the game
  315. ;;
  316.  
  317. ;; Set up a new game.
  318. (define (new-game)
  319.   (initialize-playing-area)
  320.   (set-ace-low)
  321.   (make-standard-deck)
  322.   (shuffle-deck)
  323.   
  324.   ;; set up the board
  325.  
  326.   ; freecells
  327.   (add-normal-slot '())            ; 0
  328.   (set! HORIZPOS (- HORIZPOS (/ 1 24)))
  329.   (add-normal-slot '())            ; 1
  330.   (set! HORIZPOS (- HORIZPOS (/ 1 24)))
  331.   (add-normal-slot '())            ; 2
  332.   (set! HORIZPOS (- HORIZPOS (/ 1 24)))
  333.   (add-normal-slot '())            ; 3
  334.   (set! HORIZPOS (+ HORIZPOS 0.25))
  335.  
  336.   ; homecells
  337.   (add-normal-slot '())            ; 4
  338.   (set! HORIZPOS (- HORIZPOS (/ 1 24)))
  339.   (add-normal-slot '())            ; 5
  340.   (set! HORIZPOS (- HORIZPOS (/ 1 24)))
  341.   (add-normal-slot '())            ; 6
  342.   (set! HORIZPOS (- HORIZPOS (/ 1 24)))
  343.   (add-normal-slot '())            ; 7
  344.   (add-carriage-return-slot)
  345.  
  346.   ; fields
  347.   (add-extended-slot '() down)        ; 8
  348.   (add-extended-slot '() down)        ; 9
  349.   (add-extended-slot '() down)        ; 10
  350.   (add-extended-slot '() down)        ; 11
  351.   (add-extended-slot '() down)        ; 12
  352.   (add-extended-slot '() down)        ; 13
  353.   (add-extended-slot '() down)        ; 14
  354.   (add-extended-slot '() down)        ; 15
  355.  
  356.   (add-blank-slot)
  357.   (deal-initial-setup)
  358.   (update-auto club 0)
  359.   (update-auto diamond 0)
  360.   (update-auto heart 0)
  361.   (update-auto spade 0)
  362.  
  363.   (set! board-hash (make-hash-table hash-size))
  364.   
  365.  
  366.   (list 8 3.5)
  367. )
  368.  
  369. (define (button-pressed slot card-list)
  370.   (cond ((homecell?   slot) #f)
  371.     ((field?      slot) (field-sequence? card-list))
  372.     ((freecell?   slot) #t)))
  373.  
  374. (define (droppable? start-slot card-list end-slot)
  375.         (and (not (= start-slot end-slot))
  376.              (cond
  377.                ((homecell? end-slot) (movable-to-homecell? card-list end-slot))
  378.                ((field?    end-slot) (movable-to-field? start-slot card-list end-slot))
  379.            ((freecell? end-slot) (movable-to-freecell? card-list end-slot))
  380.                (else #f))))
  381.  
  382. (define (button-released start-slot card-list end-slot)
  383.     (and
  384.         (not (= start-slot end-slot))
  385.         (cond
  386.             ((homecell? end-slot) (move-to-homecell card-list end-slot))
  387.             ((field?    end-slot) (move-to-field    start-slot card-list end-slot))
  388.             ((freecell? end-slot) (move-to-freecell card-list end-slot))
  389.         )
  390.         (move-low-cards 0)
  391.     )
  392. )
  393.   
  394. (define (button-clicked slot)
  395.   ; (FIXME)
  396.   #f)
  397.  
  398. (define (button-double-clicked slot)
  399.     (and
  400.         (not (empty-slot? slot))
  401.         (let ((card (get-top-card slot)))
  402.             (and
  403.                     (move-card-to-freecell card (any-empty-freecell))
  404.                     (remove-card slot)
  405.                     (move-low-cards 0)
  406.             )
  407.         )
  408.     )
  409. )
  410.  
  411. ;; Condition for fail -- no more cards to move
  412. (define (game-over)
  413.   ; (FIXME)
  414.   (not (game-won)))
  415.  
  416. ;; Condition for win -- all the cards in homecells
  417. (define (game-won)
  418.   (and (= 13 (length (get-cards homecell-1)))
  419.        (= 13 (length (get-cards homecell-2)))
  420.        (= 13 (length (get-cards homecell-3)))
  421.        (= 13 (length (get-cards homecell-4)))))
  422.  
  423. (define (get-options) 
  424.   #f)
  425.  
  426. (define (apply-options options) 
  427.   #f)
  428.  
  429. (define (timeout) 
  430.   ; (FIXME)
  431.   #f)
  432.  
  433. ;------------------------------------------------------------------------------
  434. ; Additions for hint feature
  435. ;
  436. ; Written by Matthew V. Ball <mball@siliconashes.net>
  437. ;
  438. ; The rest of this file is devoted to implementing an intelligent hint
  439. ; feature.  The general search algorithm creates a tree, with each unique
  440. ; board position representing a node.  These nodes are stored in a hash
  441. ; table so that the search does not repeat the work for a particular
  442. ; board position.  Furthermore, the move function sorts the cards within
  443. ; a given board so that different card orders are still treated as the 
  444. ; same board.
  445. ;
  446. ; Each searched board is given a qualitative value based first
  447. ; on "Mobility", then "Weight", then "Depth".  Here is a brief definition of
  448. ; these terms:
  449. ;
  450. ; Mobility - The maximum number of cards possible to move from one tableau
  451. ;   to another.  This equals (1 + (# of freecells)) * 2^(# of open tableaus))
  452. ; Weight - The number of cards in play that are not part of a sequence.
  453. ;   For example, placing a 5 on a 6 reduces the board weight by 1, unless the
  454. ;   5 was already on a different 6.
  455. ; Depth - The number of moves between the current node and the best node.
  456. ;
  457. ; In particular, the algorithm maximizes Mobility up until mobility-thresh,
  458. ; after which point additional mobility is not considered.  Both Weight and
  459. ; Depth are minimized.  By minimizing Depth, the algorithm will tend to
  460. ; optimize for the shortest path, eliminating unnecessary moves.  This
  461. ; becomes especially important when determining which of two winning moves
  462. ; to make (there are generally two winning moves: the last move made, if
  463. ; it is reversible, and the winning move that approaches the final solution).
  464. ;
  465. ; This algorithm will eventually find a solution, or determine that a
  466. ; solution is not possible.  However, in the interest of not searching for
  467. ; too long, the search algorithm will stop searching after a specified number
  468. ; of nodes, then return the best move found so far.  If the user presses
  469. ; help multiple times, then the search algorithm starts again where it left
  470. ; off and finds a better move.  If the search algorithm ever does find a
  471. ; solution, it will remember the entire solution in the hash table so that
  472. ; the hint feature can immediately return the next move when asked to do so.
  473. ;
  474. ; Here are definitions for some generic data structures used in this
  475. ; algorithm:
  476. ;
  477. ; Board vector - The board positions are stored in vectors (for no particular
  478. ;   reason -- I wanted to experiment with different data types).
  479. ;
  480. ; index    description
  481. ; ----- -----------
  482. ; 0-3    Freecell cards - Card list containing card, or '() if empty
  483. ; 4-7    Homecells - integer with highest card on homecell for each suit.
  484. ; 8-15    Field cards - Card list containing cards on each tableau.
  485. ;
  486. ; Board Attributes - This is a vector containg some working information
  487. ;   about an associated board.
  488. ;
  489. ; index description
  490. ; ----- -----------
  491. ; 0    Board mobility (size of largest group that can move to a field) (scalar)
  492. ; 1    Board weight (Number of groups in fields and freecells) (scalar)
  493. ; 2    Board outcome (win = 1, lose = 0, unknown = #f)
  494. ; 3    Depth to best board outcome
  495. ; 4    Inuse: Is this board currently being looked at? (#t or #f)
  496. ; 5    Best known value of sub-tree
  497. ; 6    List of possible moves, sorted from best to worst (#f if not generated)
  498. ;    Move definition: ((next-board . next-attributes) 
  499. ;                         start-slot card card-count end-slot)
  500. ;
  501. ; The hash table stores associated pairs of the board vector and board
  502. ; attributes.  This is often described as (board . info) in the following text.
  503.  
  504. ;;-----------------------------------------------------------------------------
  505. ;; Constants
  506.  
  507. ; Set debug to #t for verbose output 
  508. (define debug #f)
  509.  
  510. ; These constants refer to indices within a board attributes vector
  511. (define index-mobility 0)
  512. (define index-weight   1)
  513. (define index-outcome  2)
  514. (define index-depth    3)
  515. (define index-inuse    4)
  516. (define index-value    5)
  517. (define index-moves    6)
  518.  
  519. ; These constants are the possible values for a board outcome
  520. (define outcome-win    1)
  521. (define outcome-lose   0)
  522. (define outcome-unknown #f)
  523.  
  524. ; This is the highest mobility for which the algorithm strives.
  525. ; Any mobility larger than the threshhold is disregarded.
  526. ; 6 represents an open tableau and two cards in the reserve slots
  527. ; (generally, if the algorithm can create an open tableau, the game
  528. ; can be solved)
  529. (define mobility-thresh 6)
  530.  
  531. ; These constants indicate which board vector indices represent the state
  532. ; of the homecells.
  533. (define board-foundation 4)
  534. (define board-club    (+ board-foundation club))
  535. (define board-diamond (+ board-foundation diamond))
  536. (define board-heart   (+ board-foundation heart))
  537. (define board-spade   (+ board-foundation spade))
  538.  
  539. ; These constants affect the hash table and search algorithm
  540. (define hash-size (- (expt 2 17) 1)) ; A Mersenne prime (2^17 - 1) ~128k
  541. (define board-node-max  50) ; number of board positions to visit each time.
  542. (define traverse-node-max 1000) ; prevents stack overflow
  543.  
  544. ; These constants define values used in constructing the board value.
  545. (define weight-factor   100) ; effect of weight on final score
  546. (define mobility-factor (* 100 weight-factor)) ; effect of mobility
  547. (define max-move-value  (* 1280 mobility-factor)) ; solution found
  548. (define min-move-value  (- 0 max-move-value))     ; no solution found
  549.  
  550. ; value-bias is the amount to bias the previously best move value when
  551. ; searching sub-trees.  A more negative number tends to favor a depth-first
  552. ; search instead of a breadth-first search.
  553. (define value-bias      -50)
  554.  
  555. ;;-----------------------------------------------------------------------------
  556. ;; Global variables
  557.  
  558. ; This is a hash table that holds information about the board
  559. ; positions analyzed by the search function.
  560. (define board-hash #()) ; This variable is initialized in new-game
  561. (define visited-nodes 0) ; Number of board positions created for this search.
  562. (define traversed-nodes 0) ; Number of board positions traversed through
  563.  
  564.  
  565. ;;-----------------------------------------------------------------------------
  566. ;; Functions
  567.  
  568. ; Returns the best move found by the search algorithm
  569. (define (get-hint)
  570.   (if debug (display "get-hint\n"))
  571.   (set! visited-nodes 0)
  572.   (set! traversed-nodes 0)
  573.   (let* ((board (copy-master-board))
  574.      (info  (get-board-info board)))
  575.     (analyze-board board info 0)
  576.     (let* ((moves (vector-ref info index-moves)))
  577.       (if debug
  578.     (begin
  579.       (display "visited nodes: ") (display visited-nodes) (newline)
  580.       (display "traversed nodes: ") (display traversed-nodes) (newline)
  581.       (display (list-head (vector->list info) 6))
  582.       (newline)
  583.       (display-moves board moves)
  584.       (newline)
  585.       (display-best-move-trace board moves)))
  586.       (create-help-list board moves))))
  587.  
  588. ; Displays the sequence of best moves found so far by the search. (Debug only)
  589. ; Note that the best sequence is occasionally not available depending on
  590. ; how the hint function terminates the search.  In these cases, this function
  591. ; displays "Non-decreasing" and shows the available moves at the point
  592. ; it got confused.
  593. ; move format: ((board . info) start-slot card card-count end-slot)
  594. (define (display-best-move-trace board moves)
  595.   (if (not (or (null? moves)
  596.            (eq? moves #f)))
  597.     (let* ((best-move (car moves))
  598.        (next-moves (vector-ref (cdar best-move) index-moves)))
  599.       (display-moves board (list best-move))
  600.       (if (not (or (null? next-moves) (eq? next-moves #f)))
  601.     (if (> (vector-ref (cdar best-move) index-depth)
  602.            (vector-ref (cdaar next-moves) index-depth))
  603.       (display-best-move-trace (caar best-move) next-moves)
  604.       (begin
  605.         (display "Non Decreasing:\n")
  606.         (display-moves board moves)
  607.         (display "Trace of best-move:\n")
  608.         (display-moves (caar best-move) next-moves)))))))
  609.  
  610. ; Displays a list of moves, relative to a given board position (debug only)
  611. ; move format: ((board . info) start-slot card card-count end-slot)
  612. (define (display-moves board moves)
  613.   (if (not (null? moves))
  614.     (begin
  615.       (display (list-head (vector->list (cdaar moves)) 6))
  616.       (display (create-help-list board moves))
  617.       (newline)
  618.       (display-moves board (cdr moves)))))
  619.  
  620. ; Creates the move description returned by get-hint.
  621. ; move format: ((board . info) start-slot card card-count end-slot)
  622. (define (create-help-list board moves)
  623.   (if (null? moves)
  624.     (list 0 (_"No moves are possible. Undo or start again."))
  625.     (let* ((best-move (car moves))
  626.        (from-card (caddr best-move))
  627.        (to-slot   (list-ref best-move 4))
  628.        (to-stack  (vector-ref board to-slot)))
  629.       (if (eq? (vector-ref (cdar best-move) index-outcome) outcome-lose)
  630.     (list 0 (_"The game has no solution. Undo or start again."))
  631.     (list 1 (get-name from-card)
  632.           (cond ((freecell? to-slot)  (_"an empty reserve"))
  633.             ((homecell? to-slot)  (_"the foundation"))
  634.             ((null? to-stack)     (_"an open tableau"))
  635.             (else (get-name (car to-stack)))))))))
  636.  
  637. ; Returns a vector copy of the master board for use as the initial
  638. ; node in the search.
  639. (define (copy-master-board)
  640.   (let ((freecell-cards (map get-cards freecells))
  641.     (homecell-cards (list highest-club
  642.                   highest-diamond
  643.                   highest-heart
  644.                   highest-spade))
  645.     (field-cards    (map get-cards fields)))
  646.     (list->vector (append
  647.             (sort freecell-cards compare-cards)
  648.             homecell-cards
  649.             (sort field-cards compare-cards)))))
  650.  
  651. ; Recursively analyzes board positions.  This function is the heart of
  652. ; the search algorithm.  It will continue to search sub-nodes as long as
  653. ; each newly searched board has a value that is greater than prev-best.
  654. ; Otherwise, this function saves the value of the best board position found
  655. ; in this sub-tree, and returns to the caller
  656. ;
  657. ; Parameters:
  658. ;   board - vector containing board position to analyze
  659. ;   info - vector describing board (board attributes)
  660. ;   prev-best - best board value seen in nodes above this node.
  661. (define (analyze-board board info prev-best)
  662.   ; increment the number of traversed nodes so that we can estimate the
  663.   ; stack depth and ensure it doesn't get too deep.
  664.   (set! traversed-nodes (+ 1 traversed-nodes))
  665.   
  666.   ; Check wether we have already generated moves for this board position.
  667.   ; If not generate the moves now.
  668.   (if (eq? (vector-ref info index-moves) #f)
  669.     (vector-set! info index-moves (get-board-moves board)))
  670.   (vector-set! info index-inuse (+ 1 (vector-ref info index-inuse)))
  671.  
  672.   ; set this node to outcome-lose so that we don't revisit the same node.
  673.   ; This also becomes the default value if we return early
  674.   (vector-set! info index-value min-move-value)
  675.   (vector-set! info index-outcome outcome-lose)
  676.  
  677.   ; Sort the moves from best to worst based on value
  678.   (let ((moves (sort (vector-ref info index-moves) move-compare)))
  679.     (vector-set! info index-moves moves)
  680.  
  681.     ; Check whether there are any moves that don't lose.  (If not, exit
  682.     ; with loss)
  683.     (if (and (not (null? moves))
  684.          (not (eq? (vector-ref (cdaar moves) index-outcome) outcome-lose)))
  685.  
  686.       ; Determine whether to traverse deeper, or to go back up the tree
  687.       (if (and (eq? (vector-ref (cdaar moves) index-outcome) #f)
  688.            (< visited-nodes board-node-max)
  689.            (< traversed-nodes traverse-node-max)
  690.            (>= (vector-ref (cdaar moves) index-value) prev-best))
  691.     (begin
  692.       ; Traverse into the best available move
  693.       (analyze-board 
  694.         (caaar moves)
  695.         (cdaar moves)
  696.         (if (null? (cdr moves))
  697.           prev-best
  698.           (max prev-best (+ value-bias
  699.                 (vector-ref (cdaadr moves) index-value)))))
  700.       ; Repeat analysis of this node in case another move beats the
  701.       ; current best
  702.       (analyze-board board info prev-best))
  703.  
  704.     ; Copy the best outcome and move to previous node
  705.     (copy-outcome-info! info (cdaar moves)))
  706.       ; else leave outcome set to 'outcome-lose' and go up to previous node
  707.       ))
  708.   (vector-set! info index-inuse (+ -1 (vector-ref info index-inuse))))
  709.  
  710. ; copies the inportant board information from source to dest
  711. (define (copy-outcome-info! dest source)
  712.   (vector-set! dest index-outcome     (vector-ref source index-outcome))
  713.   (vector-set! dest index-value (+ -1 (vector-ref source index-value)))
  714.   (vector-set! dest index-depth (+  1 (vector-ref source index-depth))))
  715.  
  716. ; Sort compare function -- compares two moves (see also get-move-value)
  717. ; Rules:
  718. ;  if a position is a winner, move it to the front.
  719. ;  else if a position is a loser, move it to the back.
  720. ;  else if the mobility of both positions is above a threshold, then
  721. ;       compare positions only using board weight
  722. ;  else compare using mobility first, then use board weight for a tie,
  723. ;       then use depth as a further tie-breaker.
  724. ;
  725. ; returns #t if left move is better than right move
  726. ; returns #f if both positions are equal or right move is better
  727. ; input format: ((board . info) start-slot card card-count end-slot)
  728. (define (move-compare left right)
  729.   (> (vector-ref (cdar left)  index-value)
  730.      (vector-ref (cdar right) index-value)))
  731.  
  732. ; Returns a list of possible board moves
  733. (define (get-board-moves board)
  734.   (get-board-moves-from-slots 
  735.     board (append fields freecells)))
  736.  
  737. ; Returns a list of board moves from a given list of slots
  738. (define (get-board-moves-from-slots board slots)
  739.   (if (null? slots)
  740.     '()
  741.     (append (get-board-moves-from-cards 
  742.           board 
  743.           (car slots)
  744.           1
  745.           (vector-ref board (car slots)))
  746.         (get-board-moves-from-slots board (cdr slots)))))
  747.  
  748. ; Returns a list of board moves from a given slot with a given height of cards
  749. (define (get-board-moves-from-cards board slot height cards)
  750.   (if (null? cards) 
  751.     '()
  752.     (append (if (and (not (null? (cdr cards)))
  753.              (field-join? (car cards) (cadr cards)))
  754.           (get-board-moves-from-cards 
  755.         board 
  756.         slot 
  757.         (+ height 1) 
  758.         (cdr cards))
  759.           '() )
  760.         (get-moves-from-card-to-slots
  761.           board 
  762.           slot 
  763.           height 
  764.           (car cards)
  765.           (append 
  766.         (remove-redundant-open-slots board fields)
  767.         (get-leftmost-open-freecell board)
  768.         homecells)))))
  769.  
  770. ; returns a list containing the slot number for the left-most open freecell,
  771. ; or '() if none are open
  772. (define (get-leftmost-open-freecell board)
  773.   (cond ((null? (vector-ref board freecell-1)) (list freecell-1))
  774.     ((null? (vector-ref board freecell-2)) (list freecell-2))
  775.     ((null? (vector-ref board freecell-3)) (list freecell-3))
  776.     ((null? (vector-ref board freecell-4)) (list freecell-4))
  777.     (else '())))
  778.  
  779. ; Returns a list of field slot numbers with redundant open slots removed
  780. (define (remove-redundant-open-slots board slots)
  781.   (if (null? slots)
  782.     '()
  783.     (if (null? (vector-ref board (car slots)))
  784.       (cons (car slots) (remove-all-open-fields board (cdr slots)))
  785.       (cons (car slots) (remove-redundant-open-slots board (cdr slots))))))
  786.  
  787. ; Returns a list of fields slot number with all open slots removed
  788. (define (remove-all-open-fields board slots)
  789.   (if (null? slots)
  790.     '()
  791.     (if (null? (vector-ref board (car slots)))
  792.       (remove-all-open-fields board (cdr slots))
  793.       (cons (car slots) (remove-all-open-fields board (cdr slots))))))
  794.  
  795. ; determines the possible moves from a given card (at a particular source-slot
  796. ; and with a given height) to a set of destination slots.
  797. (define (get-moves-from-card-to-slots board source-slot height card slots)
  798.   (if (null? slots)
  799.     '()
  800.     (append 
  801.       (let* ((dest-slot (car slots))
  802.          (dest-cards (vector-ref board dest-slot)))
  803.     (if (or (and (homecell? dest-slot)
  804.              (= height 1)
  805.              (= (get-suit card) (- dest-slot homecell-1))
  806.              (= (get-value card) (+ dest-cards 1)))
  807.             (and (freecell? dest-slot)
  808.              (not (freecell? source-slot))
  809.              (= height 1)
  810.              (null? dest-cards))
  811.             (and (field? dest-slot)
  812.              (or (and (null? dest-cards)
  813.                   (or (freecell? source-slot)
  814.                   (not
  815.                     (= height
  816.                        (length
  817.                      (vector-ref board source-slot))))))
  818.              (and (not (null? dest-cards))
  819.                   (field-join? card (car dest-cards))))
  820.              (or (= height 1)
  821.              (<= height
  822.                  (get-board-mobility
  823.                    board
  824.                    (if (null? dest-cards) 1 0))))))
  825.       (let* ((move-cdr (list source-slot card height (car slots)))
  826.          (move (cons (get-board-info-pair
  827.                    (perform-move board move-cdr))
  828.                  move-cdr)))
  829.         (if (= (vector-ref (cdar move) index-value) 0)
  830.           (vector-set! 
  831.             (cdar move) index-value
  832.             (quotient 
  833.           (get-move-value move)
  834.           (let ((source-cards (list-tail (vector-ref board source-slot)
  835.                              height)))
  836.             (if (and (not (null? source-cards))
  837.                  (not (freecell? (cadr move)))
  838.                  (field-join?
  839.                    (caddr move)
  840.                    (car source-cards)))
  841.               2
  842.               1)))))
  843.         (list move))
  844.       '() ))
  845.       (get-moves-from-card-to-slots 
  846.     board 
  847.     source-slot 
  848.     height 
  849.     card 
  850.     (cdr slots)))))
  851.  
  852. ; returns a new board with a given move applied and small cards moved up
  853. ;   board - a board vector
  854. ;   move - list in the form (source-slot card card-count dest-slot)
  855. ;          (This is more precisely a move-cdr)
  856. (define (perform-move board move)
  857.   (set! visited-nodes (+ 1 visited-nodes))
  858.   (let ((new-board (list->vector (vector->list board)))
  859.     (source-stack (vector-ref board (car move)))
  860.     (dest-stack (vector-ref board (cadddr move))))
  861.     (vector-set! new-board (cadddr move)
  862.          (if (homecell? (cadddr move))
  863.            (get-value (car source-stack))
  864.            (append (list-head source-stack (caddr move))
  865.                dest-stack)))
  866.     (vector-set! new-board (car move) (list-tail source-stack (caddr move)))
  867.     (move-board-low-cards new-board 0)
  868.     (let* ((temp-board (vector->list new-board))
  869.        (freecell-cards (list-head temp-board 4))
  870.        (homecell-cards (list-head (list-tail temp-board 4) 4))
  871.        (field-cards (list-tail temp-board 8)))
  872.       (set! new-board 
  873.     (list->vector (append (sort freecell-cards compare-cards)
  874.                   homecell-cards
  875.                   (sort field-cards compare-cards)))))
  876.     new-board))
  877.  
  878. ; Compares two card stacks and returns #t if the top card from
  879. ; card1 is larger than that of card2.
  880. (define (compare-cards card1 card2)
  881.   (> (card-value card1) (card-value card2)))
  882.  
  883. ; Returns 0 if there is no card, or between 1 and 52 for the absolute
  884. ; rank of the top card in a stack.  This equates to 4*rank+suit, where
  885. ; the suit order is club=0, diamond=1, heart=2, and spade=3.
  886. ; format of card: ((rank suit visible) ...) or '()
  887. (define (card-value card)
  888.   (if (null? card)
  889.     0
  890.     (+ (* 4 (caar card)) (cadar card))))
  891.  
  892. ; This function is more or less a copy of move-low-cards, except it
  893. ; operates on a local board instead of a global board.
  894. (define (move-board-low-cards board slot)
  895.   (and (not (homecell? slot))
  896.        (not (null? (vector-ref board slot)))
  897.        (let* ((card (car (vector-ref board slot)))
  898.           (homecell-slot (+ board-foundation (get-suit card)))
  899.           (homecell-value (vector-ref board homecell-slot)))
  900.      (if (and (= (get-value card) (+ 1 homecell-value))
  901.           (or (and (= (get-color card) red)
  902.                (<= (get-value card) (max-board-auto-red board)))
  903.               (and (= (get-color card) black)
  904.                (<= (get-value card) (max-board-auto-black board)))))
  905.        (begin
  906.          (vector-set! board (+ board-foundation (get-suit card)) 
  907.               (get-value card))
  908.          (vector-set! board slot (cdr (vector-ref board slot)))
  909.          (move-board-low-cards board 0)))))
  910.   (or (>= slot field-8)
  911.       (move-board-low-cards board (+ 1 slot))))
  912.  
  913. ; Copy of max-auto-red, except uses a local board.
  914. ; Returns the maximum rank of the red homecells that is automatically moved.
  915. ; This equates to the highest red suit rank that is not useful in play.  In
  916. ; other words, it is better to move the lower black suit cards to the
  917. ; homecells instead of stacking them on top of a red suit card that is at or
  918. ; below this rank.
  919. (define (max-board-auto-red board)
  920.   (min (+ 2 (min (vector-ref board board-club)
  921.          (vector-ref board board-spade)))
  922.        (+ 3 (min (vector-ref board board-diamond)
  923.          (vector-ref board board-heart)))))
  924.  
  925. ; see max-board-auto-red and exchange red for black
  926. (define (max-board-auto-black board)
  927.   (min (+ 2 (min (vector-ref board board-diamond) 
  928.          (vector-ref board board-heart)))
  929.        (+ 3 (min (vector-ref board board-club) 
  930.          (vector-ref board board-spade)))))
  931.  
  932. ; Returns the value of a move, based on the board information.
  933. ; The resulting format generally looks like this: MWWDD, where
  934. ;   M is Mobility, WW is 100 - board weight, and DD is 100 - depth.
  935. ; input format: ((board . info) start-slot card card-count end-slot)
  936. (define (get-move-value move)
  937.   (let ((info  (cdar move))
  938.     (board (caar move)))
  939.     (let ((mobility (vector-ref info index-mobility))
  940.       (weight   (vector-ref info index-weight))
  941.       (outcome  (vector-ref info index-outcome))
  942.       (inuse    (> (vector-ref info index-inuse) 0))
  943.       (depth    (vector-ref info index-depth)))
  944.       (cond (inuse                      min-move-value)
  945.         ((eq? outcome outcome-win)  (- max-move-value depth))
  946.         ((= weight 0)        (- max-move-value depth))
  947.         ((eq? outcome outcome-lose) min-move-value)
  948.         (else (+ (* mobility-factor (min mobility-thresh mobility))
  949.              (- mobility-factor (* weight-factor weight))
  950.              (- weight-factor depth)))))))
  951.  
  952. ; generates a board and info pair (board . pair) based on an input board
  953. (define (get-board-info-pair board)
  954.   (cons board (get-board-info board)))
  955.  
  956. ; Returns the information for a particular board position by looking
  957. ; in hash table.  If not entry found, creates a new entry in the hash
  958. ; table with default information
  959. (define (get-board-info board)
  960.   (or (hash-ref board-hash board)
  961.       (let ((info (vector (get-board-mobility board 0)
  962.               (get-board-weight board)
  963.               outcome-unknown ; Outcome not known
  964.               1     ; each new board has a depth of 1
  965.               0     ; board is not yet being looked at
  966.               0     ; position has no value yet
  967.               #f))) ; no moves generated yet
  968.     ; Add new board to hash table
  969.     (hash-set! board-hash board info)
  970.     (if (= (vector-ref info index-weight) 0)
  971.       (vector-set! info index-outcome outcome-win))
  972.     info)))
  973.  
  974. ; Determines a board's 'Weight' by determining the number of groups within
  975. ; the tableaus and the freecells (reserves).  A group is defined as a set
  976. ; of consecutive cards that alternate color.
  977. (define (get-board-weight board)
  978.   (define (get-slot-list-weight slots)
  979.     (if (null? slots)
  980.       0
  981.       (+ (get-card-list-weight (vector-ref board (car slots)))
  982.      (get-slot-list-weight (cdr slots)))))
  983.   (get-slot-list-weight (append freecells fields)))
  984.         
  985. ; returns the 'weight' of a card list, which is the number of distinct runs
  986. (define (get-card-list-weight card-list)
  987.   (cond ((null? card-list)       0)
  988.         ((null? (cdr card-list)) 1)
  989.         (else (+ (get-card-list-weight (cdr card-list))
  990.          (if (field-join? (car card-list) (cadr card-list)) 0 1)))))
  991.  
  992. ; Returns the board 'Mobility', which is defined as the largest run of cards
  993. ; the user could move to another card.
  994. ; Parameters:
  995. ;   board: board vector
  996. ;   adjust: 0 - Compute mobility when moving a stack to another card
  997. ;           1 - Compute mobility when moving a stack to an open tableau
  998. (define (get-board-mobility board adjust)
  999.   (* (+ (get-board-free-count board freecells) 1)
  1000.      (expt 2 (- (get-board-free-count board fields) adjust))))
  1001.  
  1002. ; returns the number of open cells available within a given set of cells
  1003. (define (get-board-free-count board cells)
  1004.   (if (null? cells)
  1005.     0
  1006.     (+ (get-board-free-count board (cdr cells))
  1007.        (if (null? (vector-ref board (car cells))) 1 0))))
  1008.  
  1009. (set-features droppable-feature)
  1010.  
  1011. (set-lambda new-game button-pressed button-released button-clicked button-double-clicked game-over game-won get-hint get-options apply-options timeout droppable?)
  1012.  
  1013. ;;; freecell.scm ends here
  1014.  
  1015.  
  1016.