M.Hiroi's Home Page

xyzzy Lisp Programming

Common Lisp 入門

[ xyzzy Lisp ]

パズル「ペグ・ソリテア」解法プログラム

●12 穴盤のプログラムリスト

;
; peg12.l : ペグ・ソリテア 12 穴盤の解法(反復深化)
;
;           Copyright (C) 2002 Makoto Hiroi
;

; 跳び先表 (跳び越される位置 . 跳び先の位置)
(defvar *jump-table*  #(((2 . 5) (3 . 7))    ; 0
                        ((3 . 6) (4 . 8))    ; 1
                        ((3 . 4) (6 . 10))   ; 2
                        ((6 . 9) (7 . 11))   ; 3
                        ((3 . 2) (7 . 10))   ; 4
                        ((2 . 0) (6 . 7))    ; 5
                        ((3 . 1) (7 . 8))    ; 6
                        ((3 . 0) (6 . 5))    ; 7
                        ((4 . 1) (7 . 6))    ; 8
                        ((6 . 3) (10 . 11))  ; 9
                        ((6 . 2) (7 . 4))    ; 10 
                        ((7 . 3) (10 . 9)))) ; 11 

; ペグの移動パターンをすべて求める
(defun get-move-pattern (board)
  (let (result del to)
    (dotimes (from 12 result)
      (when (nth from board)
        (dolist (pos (aref *jump-table* from))
          (setq del (car pos)
                to  (cdr pos))
          (if (and (nth del board) (not (nth to board)))
              (push (list from del to) result)))))))

; ペグを動かす
(defun move-peg (n board pattern)
  (if board
      (cons (if (member n pattern)
                (not (car board))
                (car board))
            (move-peg (1+ n) (cdr board) pattern))))

; 反復深化
(defun solve-id (n jc limit board history)
  (when (<= jc limit)
    (if (= n 10)
        ; 解を見つけた
        (print-answer (reverse history))
        ; ペグを移動する
        (dolist (pattern (get-move-pattern board))
          (solve-id (1+ n)
                    ; 連続跳び越しのチェック
                    (if (eql (third (car history)) (first pattern))
                        jc
                        (1+ jc))
                    limit
                    (move-peg 0 board pattern)
                    (cons pattern history))))))

; 解を表示する
(defun print-answer (history)
  (let ((prev (third (car history))))
    ; 初手を表示
    (format t "[~D, ~D" (first (car history)) prev)
    ; 2 手目以降を表示
    (dolist (pos (cdr history))
      (cond ((= prev (first pos))    ; 同じ駒が続けて跳ぶ 
             (setq prev (third pos))
             (format t ",~D" prev))
            (t                       ; 違う駒が跳ぶ
             (setq prev (third pos))
             (format t "][~D, ~D" (first pos) prev))))
    (format t "]~%")
    (incf *count*)))


; ペグ・ソリテア 12 穴盤の解法
(defun solve-peg12 (pos)
  (let ((board (make-list 12 :initial-element t)))
    ; ペグをひとつ取り除く
    (setf (nth pos board) nil
          *count* 0)
    (dotimes (x 10)
      (format t "----- ~D 手 を探索 -------~%" (1+ x))
      (solve-id 0 0 (1+ x) board nil)
      (if (plusp *count*) (return)))))

戻る


●18 穴盤のプログラムリスト

;
; peg18.l : ペグ・ソリテア 18 穴盤の解法(反復深化+下限値枝刈り法)
;
;           Copyright (C) 2002 Makoto Hiroi
;

; 跳び先表 : (跳び越される位置 . 跳び越される位置)
(defvar *jump-table*  #(((2 . 5)  (3 . 7))                         ; 0
                        ((3 . 6)  (4 . 8))                         ; 1
                        ((3 . 4)  (5 . 9)  (6 . 11))               ; 2
                        ((6 . 10) (7 . 12))                        ; 3
                        ((3 . 2)  (7 . 11) (8 . 13))               ; 4
                        ((2 . 0)  (6 . 7)  (10 . 15))              ; 5
                        ((3 . 1)  (7 . 8)  (10 . 14) (11 . 16))    ; 6
                        ((3 . 0)  (6 . 5)  (11 . 15) (12 . 17))    ; 7
                        ((4 . 1)  (7 . 6)  (12 . 16))              ; 8
                        ((5 . 2)  (10 . 11))                       ; 9
                        ((6 . 3)  (11 . 12))                       ; 10
                        ((6 . 2)  (7 . 4) (10 . 9) (12 . 13))      ; 11
                        ((7 . 3)  (11 . 10))                       ; 12
                        ((8 . 4)  (12 . 11))                       ; 13
                        ((10 . 6) (15 . 16))                       ; 14
                        ((10 . 5) (11 . 7) (16 . 17))              ; 15
                        ((11 . 6) (12 . 8) (15 . 14))              ; 16
                        ((12 . 7) (16 . 15))))                     ; 17


; 下限値の計算
(defun get-lower-value (board prev)
  (let ((value 0))
    ; コーナーのチェック
    (dolist (c '(0 1 9 13 14 17))
      (if (and (nth c board) (not (eql c prev)))
          (incf value)))
    ; 辺のチェック
    (dolist (edge '((2 5) (4 8) (15 16)) value)
      (unless (member prev edge)
        (if (and (nth (first edge) board) (nth (second edge) board))
            (incf value))))))


; ペグを動かす
(defun move-peg (n board pos)
  (if board
      (cons (if (member n pos)
                (not (car board))
                (car board))
            (move-peg (1+ n) (cdr board) pos))))


; ペグの跳び方を求める (from del to)
(defun get-move-pattern (board)
  (let (result del to)
    (dotimes (from 18 result)
      (when (nth from board)
        (dolist (pos (aref *jump-table* from))
          (setq del (car pos)
                to  (cdr pos))
          (if (and (nth del board) (not (nth to board)))
              (push (list from del to) result)))))))


; 解を表示する
(defun print-answer (history)
  (let ((prev (third (car history))))
    ; 初手を表示
    (format t "[~D, ~D" (first (car history)) prev)
    ; 2 手目以降を表示
    (dolist (pos (cdr history))
      (cond ((= prev (first pos))
             (setq prev (third pos))
             (format t ",~D" prev))
            (t
             (setq prev (third pos))
             (format t "][~D, ~D" (first pos) prev))))
    (format t "]~%")
    (throw 'find-answer t)))


; 反復深化(下限値枝刈り法)
(defun solve-id (n jc limit board history)
  (when (<= (+ jc (get-lower-value board (third (car history)))) limit)
    (if (= n 16)
        (print-answer (reverse history))
      (dolist (pattern (get-move-pattern board))
        (solve-id (1+ n)
                  (if (eql (third (car history)) (first pattern))
                      jc
                      (1+ jc))
                  limit
                  (move-peg 0 board pattern)
                  (cons pattern history))))))

; ペグ・ソリテア 18 穴盤の解法
(defun solve-peg18 (pos)
  (let ((board (make-list 18 :initial-element t)))
    ; ペグをひとつ取り除く
    (setf (nth pos board) nil)
    (catch 'find-answer
      (do ((limit (get-lower-value board nil) (1+ limit)))
          ((> limit 16))
        (format t "----- ~D 手 を探索 -------~%" limit)
        (solve-id 0 0 limit board nil)))))

戻る


●18 穴盤のプログラムリスト(ビット操作版)

;
; peg18_1.l : ペグ・ソリテア 18 穴盤の解法(反復深化+下限値枝刈り法)
;             盤面を整数値で表し、ペグの状態をビットのオン・オフで表す
;
;             Copyright (C) 2002 Makoto Hiroi
;

; 跳び先表 : (跳び越される位置 . 跳び越される位置)
(defvar *jump-table1*  #(((2 . 5)  (3 . 7))                         ; 0
                         ((3 . 6)  (4 . 8))                         ; 1
                         ((3 . 4)  (5 . 9)  (6 . 11))               ; 2
                         ((6 . 10) (7 . 12))                        ; 3
                         ((3 . 2)  (7 . 11) (8 . 13))               ; 4
                         ((2 . 0)  (6 . 7)  (10 . 15))              ; 5
                         ((3 . 1)  (7 . 8)  (10 . 14) (11 . 16))    ; 6
                         ((3 . 0)  (6 . 5)  (11 . 15) (12 . 17))    ; 7
                         ((4 . 1)  (7 . 6)  (12 . 16))              ; 8
                         ((5 . 2)  (10 . 11))                       ; 9
                         ((6 . 3)  (11 . 12))                       ; 10
                         ((6 . 2)  (7 . 4) (10 . 9) (12 . 13))      ; 11
                         ((7 . 3)  (11 . 10))                       ; 12
                         ((8 . 4)  (12 . 11))                       ; 13
                         ((10 . 6) (15 . 16))                       ; 14
                         ((10 . 5) (11 . 7) (16 . 17))              ; 15
                         ((11 . 6) (12 . 8) (15 . 14))              ; 16
                         ((12 . 7) (16 . 15))))                     ; 17


; 跳び先表にビット反転用データを付加する
; データ構造は (del to bit-pattern)
(defun make-jump-table ()
  (setq *jump-table* (make-array 18))
  (dotimes (from 18)
    (dolist (pattern (aref *jump-table1* from))
      (let ((del (car pattern))
            (to  (cdr pattern)))
        (push (list del to (logior (ash 1 from) (ash 1 del) (ash 1 to)))
              (aref *jump-table* from))))))


; ペグの跳び方を求める
; データ構造は (new-board from del to)
(defun get-move-pattern (board)
  (let (result del to)
    (dotimes (from 18 result)
      (when (logbitp from board)
        (dolist (pos (aref *jump-table* from))
          (setq del (first pos)
                to  (second pos))
          (if (and (logbitp del board) (not (logbitp to board)))
              (push (list (logxor board (third pos)) from del to) result)))))))


; 下限値を求める
(defun get-lower-value (board prev)
  (unless (integerp prev) (setq prev 18))
  (let* ((corner #b100110001000000011)
         (count  (logcount (logand board corner))))
    ; コーナーのチェック
    (if (logbitp prev corner)
        (decf count))
    ; 辺のチェック
    (dolist (edge '(#b100100 #b100010000 #b11000000000000000) count)
      (unless (logbitp prev edge)
        (if (= edge (logand edge board))
            (incf count))))))


; 解を表示する
(defun print-answer (history)
  (let ((prev (third (car history))))
    ; 初手を表示
    (format t "[~D, ~D" (first (car history)) prev)
    ; 2 手目以降を表示
    (dolist (pos (cdr history))
      (cond ((= prev (first pos))
             (setq prev (third pos))
             (format t ",~D" prev))
            (t
             (setq prev (third pos))
             (format t "][~D, ~D" (first pos) prev))))
    (format t "]~%")
    (throw 'find-answer t)))


; 反復深化
(defun solve-id (n jc limit board history)
  (when (<= (+ jc (get-lower-value board (third (car history)))) limit)
    (if (= n 16)
        (print-answer (reverse history))
      ; pattern の構造は (new-board from del to) 
      (dolist (pattern (get-move-pattern board))
        (solve-id (1+ n)
                  (if (eql (third (car history)) (second pattern))
                      jc
                      (1+ jc))
                  limit
                  (first pattern)
                  (cons (cdr pattern) history))))))

; ペグ・ソリテア 18 穴盤の解法
(defun solve-peg18 (pos)
  (let ((board (logxor #x1ffffff (ash 1 pos))))
    (make-jump-table)
    (catch 'find-answer
      (do ((limit (get-lower-value board nil) (1+ limit)))
          ((> limit 16))
        (format t "----- ~D 手 を探索 -------~%" limit)
        (solve-id 0 0 limit board nil)))))

戻る


Copyright (C) 2000-2003 Makoto Hiroi
All rights reserved.

[ xyzzy Lisp ]