2022-05-24 12:57:15 -04:00
|
|
|
(defparameter *colors*
|
|
|
|
'((main . (:green :black))
|
2022-06-04 16:09:39 -04:00
|
|
|
(timer-box . (:green :black))
|
2022-05-24 12:57:15 -04:00
|
|
|
(selected-highlight . (:blue :black))
|
|
|
|
(unselected-highlight . (:white :black))))
|
|
|
|
|
|
|
|
;; Returns (y, x) to draw box to center it in screen
|
|
|
|
(defun center-box (screen width height)
|
|
|
|
(let ((sw (croatoan:width screen))
|
|
|
|
(sh (croatoan:height screen)))
|
|
|
|
(list (round (- (/ sh 2) (/ height 2))) (round (- (/ sw 2) (/ width 2))))))
|
|
|
|
|
2022-05-30 00:31:48 -04:00
|
|
|
;; Write a list of horizontal slices to the screen scr at position pos
|
2022-05-24 12:57:15 -04:00
|
|
|
(defun write-horizontal-slice-list (scr pos slices)
|
|
|
|
(let ((yi (car pos)))
|
|
|
|
(mapcar (lambda (s)
|
|
|
|
(croatoan:add scr s :position `(,yi ,(cadr pos)))
|
|
|
|
(inc yi))
|
|
|
|
slices)))
|
|
|
|
|
2022-06-01 02:23:58 -04:00
|
|
|
;; Formats a category split and a run split for the splits window
|
2022-06-04 16:09:39 -04:00
|
|
|
(defun make-split-line (csplit speedrun-split pb)
|
|
|
|
(let ((split-elapsed (run-split-elapsed-time speedrun-split))
|
|
|
|
(format-split-elapsed (run-split-format-elapsed-time speedrun-split)))
|
|
|
|
`((,(category-split-name csplit) . ,(/ 4 12))
|
|
|
|
("" . ,(/ 1 12))
|
|
|
|
(,format-split-elapsed . ,(/ 3 12))
|
|
|
|
("" . ,(/ 1 12))
|
|
|
|
(,(if pb
|
|
|
|
(let ((split-end-timestamp (ignore-errors (run-split-end-timestamp speedrun-split))))
|
|
|
|
(if split-end-timestamp
|
|
|
|
(let ((elapsed-diff (- (millis-since-internal-timestamp 0 split-elapsed) pb)))
|
|
|
|
(concatenate 'string (if (plusp elapsed-diff) "+" "-") (format-time (make-time-alist (abs elapsed-diff)))))
|
|
|
|
(format-time (make-time-alist pb))))
|
|
|
|
format-split-elapsed)
|
|
|
|
. ,(/ 3 12)))))
|
2022-06-01 02:23:58 -04:00
|
|
|
|
2022-05-30 00:31:48 -04:00
|
|
|
;; Creates a window with the total time and statistics
|
|
|
|
(defun timer-window (speedrun pos width height)
|
|
|
|
(let* ((timerglet (lispglet (format-time (make-time-alist (speedrun-elapsed speedrun)))))
|
|
|
|
(timer-box (make-instance 'croatoan:window
|
|
|
|
:border t
|
|
|
|
:position pos
|
|
|
|
:width width
|
|
|
|
:height height)))
|
2022-06-04 16:09:39 -04:00
|
|
|
(setf (croatoan:color-pair timer-box) (cdr (assoc 'timer-box *colors*)))
|
2022-05-30 00:31:48 -04:00
|
|
|
(write-horizontal-slice-list timer-box '(1 1) timerglet)
|
|
|
|
timer-box))
|
2022-05-24 12:57:15 -04:00
|
|
|
|
2022-05-30 00:31:48 -04:00
|
|
|
;; Class to hold state for a list where one element is highlighted/selected
|
2022-05-24 12:57:15 -04:00
|
|
|
(defclass highlight-list ()
|
|
|
|
((scroll-i
|
|
|
|
:initarg :scroll-i
|
|
|
|
:accessor highlight-list-scroll-i)
|
|
|
|
(elements
|
|
|
|
:initarg :elements
|
|
|
|
:accessor highlight-list-elements)
|
|
|
|
(current-element-index
|
|
|
|
:initarg :current-element-index
|
|
|
|
:accessor highlight-list-current-element-index)
|
|
|
|
(height
|
|
|
|
:initarg :height
|
|
|
|
:accessor highlight-list-height)
|
|
|
|
(width
|
|
|
|
:initarg :width
|
|
|
|
:accessor highlight-list-width)))
|
|
|
|
|
2022-05-30 00:31:48 -04:00
|
|
|
;; Create the actual window to render a highlight list hl at position pos
|
2022-05-24 12:57:15 -04:00
|
|
|
(defun highlight-list-window (hl pos)
|
2022-05-30 00:31:48 -04:00
|
|
|
(let* ((width (- (highlight-list-width hl) 2)) ;; Magic number 2's are for the border on both sides
|
2022-05-24 12:57:15 -04:00
|
|
|
(height (- (highlight-list-height hl) 2))
|
|
|
|
(elements (highlight-list-elements hl))
|
2022-05-30 00:31:48 -04:00
|
|
|
(current-element-index (mod (highlight-list-current-element-index hl) (length elements)))
|
2022-05-24 12:57:15 -04:00
|
|
|
(elements-to-draw-subseq (if (>= height (length elements))
|
2022-05-24 19:22:00 -04:00
|
|
|
(list 0 (length elements))
|
2022-05-24 12:57:15 -04:00
|
|
|
(cond
|
|
|
|
((> height (1+ current-element-index))
|
|
|
|
(list 0 height))
|
|
|
|
((< (- (length elements) height) current-element-index)
|
|
|
|
(list (- (length elements) height) (length elements)))
|
|
|
|
(t (let ((dy (/ (1- height) 2)))
|
|
|
|
(list (- current-element-index (floor dy)) (1+ (+ current-element-index (ceiling dy)))))))))
|
|
|
|
(highlight-menu (make-instance 'croatoan:window
|
|
|
|
:border t
|
2022-05-30 00:31:48 -04:00
|
|
|
:width (+ 2 width) ;; Another magic 2
|
2022-05-24 12:57:15 -04:00
|
|
|
:height (+ 2 height)
|
|
|
|
:position pos)))
|
|
|
|
(let ((yi 0))
|
|
|
|
(mapcar (lambda (el)
|
|
|
|
(setf (croatoan:color-pair highlight-menu)
|
|
|
|
(if (equal (+ yi (car elements-to-draw-subseq)) current-element-index)
|
|
|
|
(cdr (assoc 'selected-highlight *colors*))
|
|
|
|
(cdr (assoc 'unselected-highlight *colors*))))
|
|
|
|
(inc yi)
|
2022-05-24 19:22:00 -04:00
|
|
|
(croatoan:add highlight-menu
|
|
|
|
(format-line el width (highlight-list-scroll-i hl))
|
|
|
|
:position `(,yi 1)))
|
2022-05-24 12:57:15 -04:00
|
|
|
(subseq elements (car elements-to-draw-subseq) (cadr elements-to-draw-subseq))))
|
|
|
|
highlight-menu))
|
2022-05-30 00:31:48 -04:00
|
|
|
|
2022-06-01 02:23:58 -04:00
|
|
|
;; The big bad monolithic UI loop
|
2022-05-31 01:45:29 -04:00
|
|
|
(defun speedrun-ui (category)
|
2022-05-24 12:57:15 -04:00
|
|
|
(croatoan:with-screen (scr :input-blocking nil :input-echoing nil :cursor-visible nil :enable-colors t :input-buffering nil :input-blocking nil)
|
|
|
|
(setf (croatoan:background scr) (make-instance 'croatoan:complex-char :color-pair (cdr (assoc 'main *colors*))))
|
2022-06-04 16:09:39 -04:00
|
|
|
|
|
|
|
;; Create a closure over the UI state
|
2022-05-31 01:45:29 -04:00
|
|
|
(let* ((scroll 0)
|
|
|
|
(frame 0)
|
|
|
|
(state 'TITLE)
|
2022-05-30 00:31:48 -04:00
|
|
|
(redraws '(title-instance))
|
2022-05-31 01:45:29 -04:00
|
|
|
(speedrun (make-speedrun category))
|
2022-06-04 16:16:23 -04:00
|
|
|
(csplits (category-splits category))
|
2022-06-04 16:28:23 -04:00
|
|
|
(bests (statistics category))
|
2022-06-04 16:09:39 -04:00
|
|
|
(split-pbs (cdr (assoc 'SPLIT-PBS bests)))
|
|
|
|
(best-category-run-pbs (cdr (assoc 'BEST-CATEGORY-RUN-SPLITS bests))))
|
|
|
|
|
2022-05-31 01:45:29 -04:00
|
|
|
(flet ((render ()
|
|
|
|
(case state
|
|
|
|
('TITLE
|
|
|
|
(if (member 'title-instance redraws)
|
|
|
|
(croatoan:clear scr)
|
2022-05-31 13:31:37 -04:00
|
|
|
(let* ((padding 4)
|
2022-05-31 17:56:24 -04:00
|
|
|
(title (append *lispruns-logo* '("" "CONTROLS" " SPACE to start and to continue to the next split" " Q to quit")))
|
2022-05-31 13:31:37 -04:00
|
|
|
(width (+ (* 2 padding) (max-length title)))
|
|
|
|
(height (+ (* 2 padding) (length title)))
|
2022-05-31 01:45:29 -04:00
|
|
|
(logo-centered (center-box scr width height))
|
|
|
|
(logo-box (make-instance 'croatoan:window :border t :width width :height height :position logo-centered)))
|
2022-05-31 13:31:37 -04:00
|
|
|
(if (< (croatoan:width scr) width)
|
|
|
|
(progn
|
|
|
|
(croatoan:add scr "Please increase width of your terminal" :position '(0 0))
|
|
|
|
(croatoan:refresh scr))
|
|
|
|
(progn
|
|
|
|
(write-horizontal-slice-list logo-box `(,padding ,padding) title)
|
|
|
|
(croatoan:refresh logo-box))))))
|
2022-05-31 01:45:29 -04:00
|
|
|
('RUNNING
|
2022-05-31 13:31:37 -04:00
|
|
|
(if (eq (speedrun-state speedrun) 'RUNNING)
|
|
|
|
(update-time speedrun))
|
2022-05-31 01:45:29 -04:00
|
|
|
(if (member 'timer-instance redraws)
|
|
|
|
(croatoan:clear scr))
|
|
|
|
(if (zerop (mod frame 4))
|
2022-05-31 13:31:37 -04:00
|
|
|
(let* ((max-width (min 90 (croatoan:width scr)))
|
|
|
|
(centered-x (cadr (center-box scr max-width 0)))
|
|
|
|
(timer-height 8)
|
|
|
|
(splits-height (- (croatoan:height scr) timer-height))
|
2022-05-31 01:45:29 -04:00
|
|
|
(split-list (make-instance 'highlight-list
|
|
|
|
:scroll-i scroll
|
2022-05-31 17:56:24 -04:00
|
|
|
:current-element-index (speedrun-current-split-index speedrun)
|
2022-05-31 13:31:37 -04:00
|
|
|
:height splits-height
|
|
|
|
:width max-width
|
2022-06-01 02:23:58 -04:00
|
|
|
;; Todo: add personal bests to elements
|
2022-06-04 16:09:39 -04:00
|
|
|
:elements (mapcar 'make-split-line csplits (speedrun-splits speedrun) best-category-run-pbs)))
|
2022-05-31 13:31:37 -04:00
|
|
|
(splits-instance (highlight-list-window split-list `(0 ,centered-x)))
|
|
|
|
(timer-instance (timer-window speedrun `(,splits-height ,centered-x) max-width timer-height)))
|
2022-05-31 01:45:29 -04:00
|
|
|
(croatoan:refresh splits-instance)
|
|
|
|
(croatoan:refresh timer-instance)))))
|
|
|
|
(setf redraws '()
|
|
|
|
frame (mod (1+ frame) 60))
|
|
|
|
(if (zerop (mod frame 30))
|
|
|
|
(inc scroll))
|
|
|
|
(sleep (/ 1 60))))
|
2022-06-01 02:23:58 -04:00
|
|
|
|
2022-05-31 01:45:29 -04:00
|
|
|
(croatoan:event-case (scr event)
|
|
|
|
(#\q (return-from croatoan:event-case))
|
|
|
|
(#\space
|
|
|
|
(case state
|
|
|
|
('TITLE
|
|
|
|
(start-speedrun speedrun)
|
|
|
|
(setf redraws '(timer-instance))
|
|
|
|
(setf state 'RUNNING))
|
|
|
|
('RUNNING (next-split speedrun))))
|
2022-05-31 13:31:37 -04:00
|
|
|
(:resize
|
|
|
|
(case state
|
|
|
|
('TITLE
|
|
|
|
(setf redraws '(title-instance)))
|
|
|
|
('RUNNING
|
|
|
|
(croatoan:clear scr)))
|
|
|
|
(render))
|
2022-05-31 01:45:29 -04:00
|
|
|
((nil) (render)))))))
|