(in-package :de.anvi.croatoan.test)

;; Example from "Land of Lisp", Copyright by Conrad Barski.
;; Source: http://landoflisp.com/evolution.lisp
;; Unknown licence.

(defparameter *width*  100)
(defparameter *height* 30)
(defparameter *jungle* '(45 10 10 10))
(defparameter *plant-energy* 80)
(defparameter *plants* (make-hash-table :test #'equal))
(defparameter *reproduction-energy* 200)

(defstruct animal x y energy dir genes)


(defun random-plant (left top width height)
  (let ((pos (cons (+ left (random width))
                   (+ top  (random height)))))
    (setf (gethash pos *plants*) t)))

(defun add-plants ()
  ;; First one in the jungle.
  (apply #'random-plant *jungle*)
  ;; Then one in the rest of the world.
  (random-plant 0 0 *width* *height*))

;; Define one starting animal.
(defparameter *animals*
  (list (make-animal :x (ash *width* -1)
                     :y (ash *height* -1)
                     :energy 1000
                     :dir 0
                     :genes (loop repeat 8 collect (1+ (random 10))))))

(defun move- (animal)
  (let ((dir (animal-dir animal))
        (x   (animal-x animal))
        (y   (animal-y animal)))

    ;; If x>width, wrap around the world border with the mod function.
    (setf (animal-x animal)
          (mod (+ x (cond ((and (>= dir 2) (< dir 5)) 1)
                          ((or  (=  dir 1) (= dir 5)) 0)
                          (t -1))
                  *width*)
               *width*))

    ;; Down is +1, up is -1.
    (setf (animal-y animal)
          (mod (+ y (cond ((and (>= dir 0) (< dir 3)) -1)
                          ((and (>= dir 4) (< dir 7))  1)
                          (t 0))
                  *height*)
               *height*))

    (decf (animal-energy animal))))

(defun turn (animal)
  (let ((x (random (apply #'+ (animal-genes animal)))))
    (labels ((angle (genes x)
               (let ((xnu (- x (car genes))))
                 (if (< xnu 0)
                     0
                     (1+ (angle (cdr genes) xnu))))))

      (setf (animal-dir animal)
            (mod (+ (animal-dir animal)
                    (angle (animal-genes animal) x))
                 8)))))

(defun eat (animal)
  (let ((pos (cons (animal-x animal) (animal-y animal))))
    (when (gethash pos *plants*)
      (incf (animal-energy animal) *plant-energy*)
      (remhash pos *plants*))))

(defun reproduce (animal)
  (let ((e (animal-energy animal)))
    (when (>= e *reproduction-energy*)
      (setf (animal-energy animal) (ash e -1))
      (let ((animal-nu (copy-structure animal))
            (genes     (copy-list (animal-genes animal)))
            (mutation  (random 8)))
        ;; mutate a random gene by +1 or -1.
        (setf (nth mutation genes)
              (max 1 (+ (nth mutation genes) (random 3) -1)))
        ;; deep copy of the gene list.
        (setf (animal-genes animal-nu) genes)
        ;; push the new animal to the list.
        (push animal-nu *animals*)))))

(defun update-world ()
  ;; Remove dead animals.
  (setf *animals*
        (remove-if (lambda (animal) (<= (animal-energy animal) 0))
                   *animals*))
  ;; Do what animals do.
  (mapc (lambda (animal)
          (turn animal)
          (move- animal)
          (eat animal)
          (reproduce animal))
        *animals*)
  ;; Grow plants.
  (add-plants))

;;; simple non-ncurses version from LOL, prints to REPL.
(defun draw-world ()
  (loop for y
        below *height*
        do (progn (fresh-line)
                  ;; beginning of the line.
                  (princ "|")
                  (loop for x
                        below *width*
                        do (princ (cond 
                                    ;; if there is one or more animals, print a M.
                                    ((some (lambda (animal) (and (= (animal-x animal) x)
                                                            (= (animal-y animal) y)))
                                           *animals*)
                                     #\M)
                                    ;; if there is a plant, print *
                                    ((gethash (cons x y) *plants*) #\*)
                                    ;; if there is neithe a plant nor an animal, print a space.
                                    (t #\space))))
                  ;; end of the line.
                  (princ "|"))))

;; enter a recursive infinite loop as the programs main loop.
(defun evolution ()
  (draw-world)
  ;; add an empty line between worlds.
  (fresh-line)
  (let ((str (read-line)))
    (cond ((equal str "quit") ())
          (t (let ((x (parse-integer str :junk-allowed t)))
               (if x
                   (loop for i
                      below x
                      do (update-world)
                      if (zerop (mod i 1000))
                      do (princ #\.))
                   (update-world))
               (evolution))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; instead of using princ to draw to stdout, use add-string to draw to the curses screen.
;; the screen has to be initialized first in the main function evolve.
(defun draw-world-croatoan (scr)
  (loop 
     for y 
     from 0
     below *height*
     do (loop 
           for x 
           from 0
           below *width*
           do (add-string scr
                          (format nil "~A"
                                  (cond 
                                    ;; if there is one or more animals, print a M.
                                    ((some (lambda (animal) (and (= (animal-x animal) x)
                                                            (= (animal-y animal) y)))
                                           *animals*)
                                     #\M)
                                    ;; if there is a plant, print *
                                    ((gethash (cons x y) *plants*) #\*)
                                    ;; if there is neithe a plant nor an animal, print a space.
                                    (t #\space)))
                          :y y
                          :x x)))
  ;; refresh the physical screen to dsplay the drawn changes.
  (refresh scr))

;; enter a recursive infinite loop as the programs main loop.
(defun evolve ()
  (with-screen (scr :input-blocking nil :input-echoing nil :cursor-visible nil)
    (clear scr)
    (setf (background scr) (make-instance 'complex-char :color-pair '(:green :white)))

    (setq *width* (width scr))
    (setq *height* (height scr))

    (loop 
       initially 
         (draw-world-croatoan scr)

       for ch = (get-char scr)

       while (or (= ch -1) (not (equal (code-char ch) #\q)))
       do
         (update-world) 
         (sleep 0.001)
         (draw-world-croatoan scr))))

;; uses tagbody to separate between event handling and body.
(defun evolve2 ()
  (with-screen (scr :input-blocking nil :input-echoing nil :cursor-visible nil)
    (clear scr)
    (setf (background scr) (make-instance 'complex-char :color-pair '(:green :white)))

    (setq *width* (width scr))
    (setq *height* (height scr))

    (draw-world-croatoan scr)
    (refresh scr)

    (loop
     (tagbody

      ;; read a char.
      (let ((ch (get-char scr)))

        ;; if it is -1, just execute the body.
        ;; this is a special event when there is no event.
        (cond ((= ch -1) (go evolution-body))

              ;; And from here we have real key/character events.
              ((equal (code-char ch) #\q) (return))))

      evolution-body
      
      (update-world) 
      (sleep 0.001)
      (draw-world-croatoan scr)
      (refresh scr)))))

;; uses key-pressed-p to separate between events and the body, instead of loop and tagbody used in 1 and 2.
(defun evolve3 ()
  (with-screen (scr :input-blocking nil :input-echoing nil :cursor-visible nil)
    (clear scr)
    ;(setf (background scr) (make-instance 'complex-char :color-pair '(:green :white)))

    (setq *width* (width scr))
    (setq *height* (height scr))

    (draw-world-croatoan scr)

    (loop
     (if (key-pressed-p scr)
         (let ((ch (get-char scr)))
           (cond ((equal (code-char ch) #\q) (return))))
       (progn     
         (update-world) 
         (sleep 0.001)
         (draw-world-croatoan scr) )))))

(defun evolve4 ()
  "Uses get-event to separate between events and the body, instead of key-pressed-p."
  (with-screen (scr :input-blocking nil :input-echoing nil :cursor-visible nil)
    (clear scr)
    ;(setf (background scr) (make-instance 'complex-char :color-pair '(:green :white)))

    (setq *width* (width scr))
    (setq *height* (height scr))

    (draw-world-croatoan scr)

    (loop
       (let ((event (get-event scr)))
         (if event
             (case event
               (#\q (return)))
             (progn     
               (update-world) 
               (sleep 0.001)
               (draw-world-croatoan scr) ))))))

(defun evolve5 ()
  "Use the event-case macro for event handling."
  (with-screen (scr :input-blocking nil :input-echoing nil :cursor-visible nil)
    (clear scr)

    (setq *width* (width scr))
    (setq *height* (height scr))

    (draw-world-croatoan scr)

    (event-case (scr event)
      (#\q (return-from event-case))
      ((nil) 
       (update-world) 
       (sleep 0.001)
       (draw-world-croatoan scr)))))

(defun update-game-state (win event)
  "Main game state update function for evolve6, called during the nil event handling."
  (declare (ignore event))
  (update-world)
  (draw-world-croatoan win))

(defun evolve6 ()
  "Use the run-event-loop for event handling."
  (with-screen (scr :input-blocking nil :input-echoing nil :cursor-visible nil)
    (clear scr)

    (setq *width* (width scr))
    (setq *height* (height scr))

    (bind scr #\q 'exit-event-loop)
    (bind scr nil 'update-game-state)

    ;; For the same effect, set :input-blocking to 1 milisecond.
    (setf (frame-rate scr) 1000)

    ;; Draw the world once before entering the main loop.
    (draw-world-croatoan scr)
    
    (run-event-loop scr)))
