Вызов функции в цикле (Common Lisp)

Я делаю консольную игру на выживание Lisp и пытаюсь добавить функцию, где до a = b показывать "." каждую секунду. Затем, когда a = b, установите для переменной «вред» значение «истина», и если / когда эта переменная имеет значение «истина», вычтите «здоровье» на 1, пока пользователь не вызовет функцию «use-medkit», а переменная «больно». установлено false, и вы выходите из обоих циклов.

Проблема, с которой я сталкиваюсь, заключается в том, что когда мне предлагается использовать функцию «use-medkit» и я набираю ее, она не оценивает ничего, что я ввожу, и продолжает вычитать 1 из «здоровья». Как я могу вызвать функцию, введенную пользователем, во время выполнения цикла?

Вот мой код:

(setq a (random 11)) ; Random from 0 - 10
(setq b (random 11)) ; ^^^^^^^^^^^^^^^^^^
(setq hurt 0)
(setq repair 0)
(setq health 999)

(defun use-medkit () 
    (setq repair 1))

(defun get-hurt ()
    (loop
        (progn 
            (setq a (random 11))
            (setq b (random 11))
            (progn  
                (princ ".")
                (sleep 1)))

        (if (eq a b) (progn     
                        (setq hurt 1)
                        (when (eq hurt 1) (progn        
                                            (format t "~%You are hurt!~%You will lose 1 hp every 10 seconds~%~%Type use-medkit to stop the bleeding~%")
                                            (loop
                                                (progn 
                                                    (- 1 health)
                                                    (sleep 10))
                                                    ;(format t "health: ~A~%" health)
                                                (when (eq repair 1) (progn 
                                                                 (return "You stopped the bleeding") (setq hurt 0) (setq repair 0))))))))))

person Ninjacop    schedule 12.04.2018    source источник
comment
Хорошим началом было бы отформатировать код таким образом, чтобы он был удобочитаемым для человека. В настоящее время это трудно читать.   -  person Rainer Joswig    schedule 12.04.2018
comment
код на самом деле показывает, что вы все еще боретесь с базовым программированием на Лиспе. Вот хорошая вводная книга, которую можно бесплатно загрузить в формате PDF или в виде печатной книги: cs.cmu.edu/~dst/LispBook/index.html   -  person Rainer Joswig    schedule 12.04.2018
comment
Эта ошибка в каком-то смысле очевидна, а в другом - немного тонка. Lisp является однопоточным (строго говоря, некоторые подразумевают поддержку нескольких потоков, но я не думаю, что вам это нужно). Ваша программа все время тратит на то, чтобы делать такие вещи, как создание случайных чисел, печать или сон. Нет точки, где он останавливается, чтобы позволить чему-либо еще работать. Вы не можете запускать use-medkit, пока эта функция не завершится, а к этому моменту уже слишком поздно. По умолчанию Lisp не имеет цикла обработки событий.   -  person Dan Robertson    schedule 13.04.2018
comment
Знайте, что в CL setq не определены переменные, поэтому то, что произойдет, если a не существует, не определено. Используйте defparameter или defvar и используйте соглашение *earmuff*, чтобы не попасть в невозможную ситуацию.   -  person Sylwester    schedule 13.04.2018


Ответы (1)


Таким образом, программа не может делать две вещи одновременно. В частности, если вы заняты печатью точек, засыпанием и вычитанием 1 из 999, вы не будете делать паузу, чтобы посмотреть, не поступит ли другая команда.

К сожалению, решить эту проблему сложно. Лучшее решение в терминале, вероятно, будет использовать что-то вроде ncurses. Кроме того, нет стандартного способа управления буферизацией ввода. Вместо этого вот простой способ сделать немного параллелизма и несколько подсказок. Вместо этого вы можете использовать подходящую асинхронную библиотеку.

(defun maybe-read (input-stream recording-stream)
  (when (listen input-stream)
    (let ((char (read-char input-stream)))
      (if (char= char #\Newline)
         t
         (progn (write-char char recording-stream) (maybe-read))))))

(defun make-partial-reader (input-stream)
  (list input-stream (make-string-output-stream)))
(defun partial-read (reader)
  (when (apply #'maybe-read reader)
     (get-output-stream-string (second reader))))

(defun how-long-until (time)
  (let ((gap
          (/ (- time (get-internal-run-time)) internal-time-units-per-second)))
    (cond ((< gap 0) (values 0 :late))
          ((<= gap 0.001) (values 0 :now))
          (T (values (- gap 0.001) :soon)))))
(defun sleep-until (time)
  (multiple-value-bind (span type)
        (how-long-until time)
    (when (> span 60) (warn “long wait!”)
    (case type
      (:late nil)
      (:now t)
      (:soon
        (sleep span)
        (unless (sleep-until time) (warn “poor timekeeping”))
        t))))
(defmacro with-prompt-and-scheduler ((schedule) (line &optional (input *standard-input*)) &body handle-line-input)
  (let ((reader (gensym)) (insched (gensym)))
    `(let ((,reader (make-partial-reader ,input) (,insched)))
        (flet ((,schedule (in fun &aux (at (+ (get-internal-run-time) (* in internal-time-units-per-second))))
                 (if (null ,insched) (push (cons at fun) schedule)
                    (loop for s on ,insched
                          for ((at2) . y) = s
                      if (< at at2)
                       do (psetf (car s) (cons at fun)
                                 (cdr s) (cons (car s) (cdr s)))
                          (finish-loop)
                      unless y do (setf (cdr s) (acons at fun nil)) (finish-loop)))))
         (loop
           (if ,insched
               (let ((,insched (pop ,insched)))
                 (when (sleep-until (car ,insched))
                   (let ((,line (partial-read ,reader)))
                     (when ,line ,@handle-line-input)))
                 (funcall (cdr ,insched)))
               (let ((,line (concatenate 'string (get-output-stream-string (second ,reader)) (read-line (first ,reader)))))
                 ,@handle-line))))))))

И тогда вы могли бы использовать это как:

(let ((count 0))
  (with-prompt-and-scheduler (schedule) (line)
    (let ((n (read-from-string line)))
      (when (realp n)
        (schedule n (let ((x (incf count))) (lambda () (format t "Ding ~a ~a~%" x count) (finish-output))))))))

И после ввода этого ввода 10, затем в следующей строке 5. Если вы сделаете это быстро, вы получите:

Ding 2 2
Ding 1 2

Первая строка появляется через 5 секунд, а вторая - через 10. Если вы работаете медленно, вы должны получить:

Ding 1 1
Ding 2 2

Первая строка появляется через 10 секунд после того, как вы вводите 10, а вторая строка через 5 секунд после того, как вы вводите 5.

Надеюсь, это может дать вам представление о том, как заставить программу делать две вещи одновременно.

person Dan Robertson    schedule 13.04.2018
comment
Спасибо! Я думал о том, чтобы сделать его проще и подойти к нему под другим углом, но это действительно помогает! - person Ninjacop; 13.04.2018
comment
Я бы порекомендовал вам сначала изучить еще немного Lisp, а затем использовать что-то вроде cl-async и ncurses. Или что вы просто пытаетесь избежать одновременного совершения нескольких событий. - person Dan Robertson; 13.04.2018