;;;====================================================== ;;; Cannibals and Missionaries Problem ;;; ;;; Another classic AI problem. The point is ;;; to get three cannibals and three missionaries ;;; across a stream with a boat that can only ;;; hold two people. If the cannibals outnumber ;;; the missionaries on either side of the stream, ;; then the cannibals will eat the missionaries. ;;; ;;; CLIPS Version 6.01 Example ;;; ;;; To execute, merely load, reset and run. ;;;====================================================== (defmodule MAIN (export deftemplate status) (export defglobal initial-missionaries initial-cannibals)) ;;;************* ;;;* TEMPLATES * ;;;************* ;;; The status facts hold the state ;;; information of the search tree. (deftemplate MAIN::status (slot search-depth (type INTEGER) (range 1 ?VARIABLE)) (slot parent (type FACT-ADDRESS SYMBOL) (allowed-symbols no-parent)) (slot shore-1-missionaries (type INTEGER) (range 0 ?VARIABLE)) (slot shore-1-cannibals (type INTEGER) (range 0 ?VARIABLE)) (slot shore-2-missionaries (type INTEGER) (range 0 ?VARIABLE)) (slot shore-2-cannibals (type INTEGER) (range 0 ?VARIABLE)) (slot boat-location (type SYMBOL) (allowed-values shore-1 shore-2)) (slot last-move (type STRING))) ;;;***************** ;;;* INITIAL STATE * ;;;***************** (defglobal MAIN ?*initial-missionaries* = 3 ?*initial-cannibals* = 3) (deffacts MAIN::initial-positions (status (search-depth 1) (parent no-parent) (shore-1-missionaries ?*initial-missionaries*) (shore-2-missionaries 0) (shore-1-cannibals ?*initial-cannibals*) (shore-2-cannibals 0) (boat-location shore-1) (last-move "No move."))) (deffacts MAIN::boat-information (boat-can-hold 2)) ;;;**************************************** ;;;* FUNCTION FOR MOVE DESCRIPTION STRING * ;;;**************************************** (deffunction MAIN::move-string (?missionaries ?cannibals ?shore) (switch ?missionaries (case 0 then (if (eq ?cannibals 1) then (format nil "Move 1 cannibal to %s.%n" ?shore) else (format nil "Move %d cannibals to %s.%n" ?cannibals ?shore))) (case 1 then (switch ?cannibals (case 0 then (format nil "Move 1 missionary to %s.%n" ?shore)) (case 1 then (format nil "Move 1 missionary and 1 cannibal to %s.%n" ?shore)) (default then (format nil "Move 1 missionary and %d cannibals to %s.%n" ?cannibals ?shore)))) (default (switch ?cannibals (case 0 then (format nil "Move %d missionaries to %s.%n" ?missionaries ?shore)) (case 1 then (format nil "Move %d missionaries and 1 cannibal to %s.%n" ?missionaries ?shore)) (default then (format nil "Move %d missionary and %d cannibals to %s.%n" ?missionaries ?cannibals ?shore)))))) ;;;*********************** ;;;* GENERATE PATH RULES * ;;;*********************** (defrule MAIN::shore-1-move ?node <- (status (search-depth ?num) (boat-location shore-1) (shore-1-missionaries ?s1m) (shore-1-cannibals ?s1c) (shore-2-missionaries ?s2m) (shore-2-cannibals ?s2c)) (boat-can-hold ?limit) => (bind ?max-missionaries (min ?s1m ?limit)) (loop-for-count (?missionaries 0 ?max-missionaries) (bind ?min-cannibals (max 0 (- 1 ?missionaries))) (bind ?max-cannibals (min ?s1c (- ?limit ?missionaries))) (loop-for-count (?cannibals ?min-cannibals ?max-cannibals) (duplicate ?node (search-depth =(+ 1 ?num)) (parent ?node) (shore-1-missionaries (- ?s1m ?missionaries)) (shore-1-cannibals (- ?s1c ?cannibals)) (shore-2-missionaries (+ ?s2m ?missionaries)) (shore-2-cannibals (+ ?s2c ?cannibals)) (boat-location shore-2) (last-move (move-string ?missionaries ?cannibals shore-2)))))) (defrule MAIN::shore-2-move ?node <- (status (search-depth ?num) (boat-location shore-2) (shore-1-missionaries ?s1m) (shore-1-cannibals ?s1c) (shore-2-missionaries ?s2m) (shore-2-cannibals ?s2c)) (boat-can-hold ?limit) => (bind ?max-missionaries (min ?s2m ?limit)) (loop-for-count (?missionaries 0 ?max-missionaries) (bind ?min-cannibals (max 0 (- 1 ?missionaries))) (bind ?max-cannibals (min ?s2c (- ?limit ?missionaries))) (loop-for-count (?cannibals ?min-cannibals ?max-cannibals) (duplicate ?node (search-depth =(+ 1 ?num)) (parent ?node) (shore-1-missionaries (+ ?s1m ?missionaries)) (shore-1-cannibals (+ ?s1c ?cannibals)) (shore-2-missionaries (- ?s2m ?missionaries)) (shore-2-cannibals (- ?s2c ?cannibals)) (boat-location shore-1) (last-move (move-string ?missionaries ?cannibals shore-1)))))) ;;;****************************** ;;;* CONSTRAINT VIOLATION RULES * ;;;****************************** (defmodule CONSTRAINTS (import MAIN deftemplate status)) (defrule CONSTRAINTS::cannibals-eat-missionaries (declare (auto-focus TRUE)) ?node <- (status (shore-1-missionaries ?s1m) (shore-1-cannibals ?s1c) (shore-2-missionaries ?s2m) (shore-2-cannibals ?s2c)) (test (or (and (> ?s2c ?s2m) (<> ?s2m 0)) (and (> ?s1c ?s1m) (<> ?s1m 0)))) => (retract ?node)) (defrule CONSTRAINTS::circular-path (declare (auto-focus TRUE)) (status (search-depth ?sd1) (boat-location ?bl) (shore-1-missionaries ?s1m) (shore-1-cannibals ?s1c) (shore-2-missionaries ?s2m) (shore-2-cannibals ?s2c)) ?node <- (status (search-depth ?sd2&:(< ?sd1 ?sd2)) (boat-location ?bl) (shore-1-missionaries ?s1m) (shore-1-cannibals ?s1c) (shore-2-missionaries ?s2m) (shore-2-cannibals ?s2c)) => (retract ?node)) ;;;********************************* ;;;* FIND AND PRINT SOLUTION RULES * ;;;********************************* (defmodule SOLUTION (import MAIN deftemplate status) (import MAIN defglobal initial-missionaries initial-cannibals)) (deftemplate SOLUTION::moves (slot id (type FACT-ADDRESS SYMBOL) (allowed-symbols no-parent)) (multislot moves-list (type STRING))) (defrule SOLUTION::recognize-solution (declare (auto-focus TRUE)) ?node <- (status (parent ?parent) (shore-2-missionaries ?m&:(= ?m ?*initial-missionaries*)) (shore-2-cannibals ?c&:(= ?c ?*initial-cannibals*)) (last-move ?move)) => (retract ?node) (assert (moves (id ?parent) (moves-list ?move)))) (defrule SOLUTION::further-solution ?node <- (status (parent ?parent) (last-move ?move)) ?mv <- (moves (id ?node) (moves-list $?rest)) => (modify ?mv (id ?parent) (moves-list ?move ?rest))) (defrule SOLUTION::print-solution ?mv <- (moves (id no-parent) (moves-list "No move." $?m)) => (retract ?mv) (printout t t "Solution found: " t t) (progn$ (?move ?m) (printout t ?move)))