;;; ==========================================
;;; SISTEMA EXPERTO DE IDENTIFICACIÓN ANIMAL
;;; ==========================================
(defvar *rules* nil) ; Nuestra base de conocimientos (reglas)
(defvar *known-facts* nil) ; Lo que vamos aprendiendo del animal actual
(defvar *asked-questions* nil)
(defun init-rules ()
"Definimos las reglas de lógica. El símbolo -> separa premisas de la conclusión."
(setf *rules*
'(
;; Reglas de clasificación base (Categorías)
(has-hair -> mammal)
(gives-milk -> mammal)
(has-feathers -> bird)
(mammal eats-meat -> carnivore)
(mammal has-pointed-teeth has-claws forward-eyes -> carnivore)
(mammal has-hooves -> ungulate)
(bird flies -> bird-prey)
(bird-ungulate black-white (not flies) -> ostrich)
;; Identificación de animales específicos
(carnivore tawny dark-spots -> cheetah)
(carnivore tawny black-stripes -> tiger)
(ungulate long-neck dark-spots (not carnivore) -> giraffe)
(ungulate black-stripes -> zebra)
(bird swims (not flies) black-white -> penguin)
(bird-prey large -> albatross)
;; --- NUEVOS ANIMALES ---
;; Un zorro: es un carnívoro, pequeño y tiene una cola peluda
(carnivore small bushy-tail -> fox)
;; Un pollo: es un ave, vive en granja y (generalmente) no vuela
(bird farm-animal (not flies) -> chicken)
))
)
;; --- FUNCIONES DE APOYO ---
(defun split-rule (rule)
"Corta la regla para saber qué necesitamos (condiciones) y qué obtenemos (conclusión)."
(let ((arrow-pos (position '-> rule)))
(if arrow-pos
(values (subseq rule 0 arrow-pos) (nth (+ arrow-pos 1) rule))
(error "La regla no tiene flecha '->': ~A" rule))))
(defun get-user-input (question)
"Le pregunta al usuario. 'Is it true?' significa '¿Es cierto?'."
(let ((fact-true (member question *known-facts* :test #'equal))
(fact-false (member `(not ,question) *known-facts* :test #'equal)))
(cond
(fact-true t)
(fact-false nil)
(t
;; Usamos ~A para insertar la pregunta en el texto
(format t "~%* Is it true that it ~A? (y/n) > " question)
(finish-output)
(let ((ans (read)))
(cond
((member ans '(y Y yes YES))
(push question *known-facts*)
t)
((member ans '(n N no NO))
(push `(not ,question) *known-facts*)
nil)
(t (format t "Please answer 'y' or 'n'.~%")
(get-user-input question))))))))
(defun check-condition (condition)
"Analiza si una condición se cumple, manejando el caso de las negaciones (not X)."
(if (and (listp condition) (eq (car condition) 'not))
(not (check-condition (cadr condition)))
(get-user-input condition)))
;; --- EL MOTOR LÓGICO ---
(defun prove-goal (goal)
"Intenta demostrar una meta buscando reglas que la respalden."
(if (member goal *known-facts* :test #'equal)
t
(loop for rule in *rules* do
(multiple-value-bind (conditions conclusion) (split-rule rule)
(when (equal goal conclusion)
(format t "~%Checking if it is a ~A..." goal)
;; Si todas las condiciones de la regla son ciertas, ¡lo encontramos!
(if (every #'check-condition conditions)
(progn
(push goal *known-facts*)
(return-from prove-goal t))))))
nil))
;; --- INTERACCIÓN PRINCIPAL ---
(defun identify-animal ()
"Función principal: limpia la memoria y recorre la lista de posibles animales."
(setf *known-facts* nil)
(init-rules)
(format t "--- System Ready! Let's find the animal ---~%")
;; Añadimos fox y chicken a la lista de búsqueda
(let ((animals '(cheetah tiger giraffe zebra penguin albatross ostrich fox chicken))
(found nil))
(dolist (animal animals)
(when (prove-goal animal)
(setf found animal)
(return)))
(format t "~%========================================")
(if found
(format t "~%*** RESULT: It is a ~A! ***" found)
(format t "~%*** RESULT: I don't know this animal. ***"))
(format t "~%========================================~%"))
)
;; Para iniciar el sistema: (identify-animal)