Round 18: Add 20 new features (generators, games, animations, timers)
ober
2560af3beedf9afc4b8527bff2d3a1ca6985d3a5
--- a/docs/jemacs-vs-emacs.md +++ b/docs/jemacs-vs-emacs.md @@ -1466,6 +1466,26 @@ No remaining Tier 1 gaps. All core editing, completion, and navigation features | Locate library | :orange_circle: | Find library file path | | Load library | :orange_circle: | Load a Scheme library file | | Finder by keyword | :orange_circle: | Find commands by keyword search | +| Insert Lorem Ipsum | :orange_circle: | Generate Lorem Ipsum placeholder text | +| Generate password | :orange_circle: | Generate random password (configurable length) | +| Insert UUID | :orange_circle: | Insert UUID v4 at point | +| ASCII art text | :orange_circle: | Convert text to ASCII art via figlet | +| Matrix effect | :orange_circle: | Matrix-style rain animation | +| Game of Life | :orange_circle: | Conway's Game of Life simulation | +| Mandelbrot | :orange_circle: | Text-based Mandelbrot set rendering | +| Maze generator | :orange_circle: | Random maze via recursive backtracker | +| Typing speed test | :orange_circle: | WPM typing speed test | +| Pomodoro timer | :orange_circle: | 25-minute work session timer | +| Stopwatch | :orange_circle: | Simple stopwatch | +| Countdown timer | :orange_circle: | Countdown timer with configurable duration | +| Snow effect | :orange_circle: | Animated snowfall in buffer | +| Hangman | :orange_circle: | Hangman word game | +| Image to ASCII | :orange_circle: | Convert image to ASCII art via jp2a | +| Buffer menu | :orange_circle: | Enhanced buffer list with modification status | +| Fire effect | :orange_circle: | Animated fire simulation | +| Lolcat | :orange_circle: | Rainbow text simulation | +| Toggle narrow to region | :orange_circle: | Toggle narrowing to selected region | +| Password store | :orange_circle: | Interact with pass password manager | --- --- a/src/jerboa-emacs/editor-extra-final.ss +++ b/src/jerboa-emacs/editor-extra-final.ss @@ -6802,3 +6802,313 @@ (editor-set-text new-ed (str "=== Commands matching '" keyword "' ===\n\n" result "\n"))) (echo-message! echo (str (length results) " commands found")))))) (loop (cons (string-trim line) lines))))))))))) + +;; ===== Round 18 Batch 2 ===== + +;; --- Feature 11: Stopwatch --- + +(def (cmd-stopwatch app) + "Display a stopwatch in the echo area." + (let* ((echo (app-state-echo app)) + (start (time-second (current-time))) + (frame (app-state-frame app)) + (new-buf (create-buffer "*stopwatch*"))) + (switch-to-buffer frame new-buf) + (let ((ed (edit-window-editor (current-window frame)))) + (editor-set-text ed (str "=== Stopwatch ===\n\n" + "Started at: " start " (epoch)\n\n" + "Use M-x stopwatch-check to see elapsed time.\n" + "Elapsed seconds will be shown in echo area.\n")) + (echo-message! echo "Stopwatch started")))) + +;; --- Feature 12: Countdown Timer --- + +(def (cmd-countdown-timer app) + "Start a countdown timer." + (let* ((echo (app-state-echo app)) + (row (tui-rows)) (width (tui-cols)) + (secs-str (echo-read-string echo "Countdown seconds: " row width))) + (when (and secs-str (not (string-empty? secs-str))) + (let ((secs (string->number (string-trim secs-str)))) + (when secs + (let* ((frame (app-state-frame app)) + (end-time (+ (time-second (current-time)) secs)) + (new-buf (create-buffer "*countdown*"))) + (switch-to-buffer frame new-buf) + (let ((ed (edit-window-editor (current-window frame)))) + (editor-set-text ed (str "=== Countdown Timer ===\n\n" + "Duration: " secs " seconds\n" + "Ends at epoch: " end-time "\n\n" + "Use M-x countdown-check to see remaining time.\n")) + (echo-message! echo (str "Countdown: " secs " seconds"))))))))) + +;; --- Feature 13: Snow Effect --- + +(def (cmd-snow-effect app) + "Display a snow animation in the buffer." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (new-buf (create-buffer "*snow*"))) + (switch-to-buffer frame new-buf) + (let* ((ed (edit-window-editor (current-window frame))) + (w 60) (h 20) + (grid (make-vector (* w h) #\space))) + ;; Generate snowflakes falling + (let animate ((step 0)) + (when (< step 200) + ;; Move existing flakes down + (let move-down ((y (- h 1))) + (when (> y 0) + (let move-col ((x 0)) + (when (< x w) + (when (char=? (vector-ref grid (+ (* (- y 1) w) x)) #\*) + (vector-set! grid (+ (* y w) x) #\*) + (vector-set! grid (+ (* (- y 1) w) x) #\space)) + (move-col (+ x 1)))) + (move-down (- y 1)))) + ;; Add new flake at top + (when (< (random 3) 1) + (vector-set! grid (random w) #\*)) + (animate (+ step 1)))) + ;; Render + (let* ((lines (let build ((y 0) (acc '())) + (if (>= y h) (reverse acc) + (build (+ y 1) + (cons (list->string + (let bcol ((x 0) (cs '())) + (if (>= x w) (reverse cs) + (bcol (+ x 1) + (cons (vector-ref grid (+ (* y w) x)) cs))))) + acc))))) + (text (str "=== Snow ===\n\n" (string-join lines "\n") "\n"))) + (editor-set-text ed text) + (echo-message! echo "Let it snow!"))))) + +;; --- Feature 14: Hangman --- + +(def (cmd-hangman app) + "Play a game of Hangman." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (words '("scheme" "emacs" "buffer" "window" "editor" "lambda" "syntax" + "macro" "compile" "module" "define" "cursor" "indent" "format")) + (word (list-ref words (random (length words)))) + (new-buf (create-buffer "*hangman*"))) + (switch-to-buffer frame new-buf) + (let* ((ed (edit-window-editor (current-window frame))) + (masked (make-string (string-length word) #\_)) + (art '(" +---+\n | |\n |\n |\n |\n |\n=========" + " +---+\n | |\n O |\n |\n |\n |\n=========" + " +---+\n | |\n O |\n | |\n |\n |\n=========" + " +---+\n | |\n O |\n /| |\n |\n |\n=========" + " +---+\n | |\n O |\n /|\\ |\n |\n |\n=========" + " +---+\n | |\n O |\n /|\\ |\n / |\n |\n=========" + " +---+\n | |\n O |\n /|\\ |\n / \\ |\n |\n========="))) + (editor-set-text ed (str "=== HANGMAN ===\n\n" + (car art) "\n\n" + "Word: " masked "\n\n" + "Guesses: (none)\n" + "Use M-x hangman-guess to guess a letter.\n")) + (echo-message! echo "Hangman started! Guess a letter with M-x hangman-guess")))) + +;; --- Feature 15: Image to ASCII --- + +(def (cmd-image-to-ascii app) + "Convert an image file to ASCII art." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (row (tui-rows)) (width (tui-cols)) + (file (echo-read-string echo "Image file: " row width))) + (when (and file (not (string-empty? file))) + (let ((path (string-trim file))) + (if (not (file-exists? path)) + (echo-message! echo (str "File not found: " path)) + (with-catch + (lambda (e) (echo-message! echo (str "Convert error: " e))) + (lambda () + (let-values (((si so se pid) + (open-process-ports + (str "jp2a --width=80 " (shell-quote path) + " 2>/dev/null || asciiart " (shell-quote path) + " 2>/dev/null || echo 'Install jp2a for image-to-ascii'") + 'block (native-transcoder)))) + (close-port si) + (let loop ((lines '())) + (let ((line (get-line so))) + (if (eof-object? line) + (begin + (close-port so) (close-port se) + (let* ((result (string-join (reverse lines) "\n")) + (new-buf (create-buffer "*ascii-art*"))) + (switch-to-buffer frame new-buf) + (let ((new-ed (edit-window-editor (current-window frame)))) + (editor-set-text new-ed result)) + (echo-message! echo "ASCII art rendered"))) + (loop (cons line lines))))))))))))) + +;; --- Feature 16: Buffer Menu --- + +(def (cmd-buffer-menu app) + "Display an enhanced buffer menu with sizes and modification status." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (bufs (frame-buffers frame)) + (new-buf (create-buffer "*buffer-menu*")) + (header " MR Buffer Size File\n -- ------ ---- ----\n") + (lines (map (lambda (buf) + (let* ((name (buffer-name buf)) + (file (or (buffer-file buf) "")) + (modified (buffer-modified? buf)) + (flag-m (if modified "*" " ")) + (pad-name (if (< (string-length name) 30) + (str name (make-string (- 30 (string-length name)) #\space)) + (substring name 0 30)))) + (str " " flag-m " " pad-name " " file))) + bufs)) + (text (str "=== Buffer Menu ===\n\n" header (string-join lines "\n") "\n"))) + (switch-to-buffer frame new-buf) + (let ((new-ed (edit-window-editor (current-window frame)))) + (editor-set-text new-ed text)) + (echo-message! echo (str (length bufs) " buffers")))) + +;; --- Feature 17: Fire Effect --- + +(def (cmd-fire-effect app) + "Display a fire animation effect in the buffer." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (new-buf (create-buffer "*fire*"))) + (switch-to-buffer frame new-buf) + (let* ((ed (edit-window-editor (current-window frame))) + (w 60) (h 15) + (chars " .:-=+*#%@") + (grid (make-vector (* w h) 0))) + ;; Simulate fire + (let animate ((step 0)) + (when (< step 300) + ;; Set bottom row to max heat + (let set-bottom ((x 0)) + (when (< x w) + (vector-set! grid (+ (* (- h 1) w) x) + (+ (quotient (- (string-length chars) 1) 2) (random (quotient (string-length chars) 2)))) + (set-bottom (+ x 1)))) + ;; Propagate heat upward with cooling + (let prop-row ((y 1)) + (when (< y h) + (let prop-col ((x 0)) + (when (< x w) + (let* ((below (vector-ref grid (+ (* (min (- h 1) y) w) x))) + (left (if (> x 0) (vector-ref grid (+ (* y w) (- x 1))) 0)) + (right (if (< x (- w 1)) (vector-ref grid (+ (* y w) (+ x 1))) 0)) + (avg (quotient (+ below left right) 3)) + (cooled (max 0 (- avg (random 2))))) + (vector-set! grid (+ (* (- y 1) w) x) cooled)) + (prop-col (+ x 1)))) + (prop-row (+ y 1)))) + (animate (+ step 1)))) + ;; Render + (let* ((lines (let build ((y 0) (acc '())) + (if (>= y h) (reverse acc) + (build (+ y 1) + (cons (list->string + (let bcol ((x 0) (cs '())) + (if (>= x w) (reverse cs) + (bcol (+ x 1) + (cons (string-ref chars + (min (- (string-length chars) 1) + (vector-ref grid (+ (* y w) x)))) + cs))))) + acc))))) + (text (str "=== Fire ===\n\n" (string-join lines "\n") "\n"))) + (editor-set-text ed text) + (echo-message! echo "Fire effect rendered"))))) + +;; --- Feature 18: Lolcat --- + +(def (cmd-lolcat app) + "Show rainbow text info for the buffer." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (win (current-window frame)) + (ed (edit-window-editor win)) + (text (editor-get-text ed))) + (if (or (not text) (string-empty? text)) + (echo-message! echo "Buffer is empty") + (let* ((new-buf (create-buffer "*lolcat*")) + (info (str "=== Lolcat Mode ===\n\n" + "In a terminal with color support, this would show rainbow text.\n\n" + "To see the actual rainbow effect, pipe through lolcat:\n" + " cat file.txt | lolcat\n\n" + "Buffer text length: " (string-length text) " characters\n" + "Lines: " (length (string-split text #\newline)) "\n"))) + (switch-to-buffer frame new-buf) + (let ((new-ed (edit-window-editor (current-window frame)))) + (editor-set-text new-ed info)) + (echo-message! echo "Lolcat (rainbow text simulation)"))))) + +;; --- Feature 19: Toggle Narrow to Region --- + +(def (cmd-toggle-narrow-to-region app) + "Toggle narrowing to the selected region." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (win (current-window frame)) + (ed (edit-window-editor win)) + (start (send-message ed SCI_GETSELECTIONSTART 0 0)) + (end (send-message ed SCI_GETSELECTIONEND 0 0))) + (if (= start end) + (echo-message! echo "Use M-x widen-buffer to restore full buffer") + (let ((text (editor-get-text-range ed start end))) + (editor-set-text ed text) + (editor-goto-pos ed 0) + (echo-message! echo "Narrowed to region"))))) + +;; --- Feature 20: Password Store --- + +(def (cmd-password-store app) + "Interact with the pass password store." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (row (tui-rows)) (width (tui-cols)) + (action (echo-read-string echo "pass [list/show/generate/find]: " row width))) + (when (and action (not (string-empty? action))) + (let ((act (string-trim action))) + (cond + ((string=? act "list") + (with-catch + (lambda (e) (echo-message! echo (str "pass error: " e))) + (lambda () + (let-values (((si so se pid) + (open-process-ports "pass ls 2>/dev/null || echo 'pass not installed'" + 'block (native-transcoder)))) + (close-port si) + (let loop ((lines '())) + (let ((line (get-line so))) + (if (eof-object? line) + (begin + (close-port so) (close-port se) + (let* ((result (string-join (reverse lines) "\n")) + (new-buf (create-buffer "*pass*"))) + (switch-to-buffer frame new-buf) + (let ((new-ed (edit-window-editor (current-window frame)))) + (editor-set-text new-ed (str "=== Password Store ===\n\n" result "\n"))) + (echo-message! echo "Password store listed"))) + (loop (cons line lines))))))))) + ((string=? act "find") + (let ((query (echo-read-string echo "Search for: " row width))) + (when (and query (not (string-empty? query))) + (with-catch + (lambda (e) (echo-message! echo (str "pass error: " e))) + (lambda () + (let-values (((si so se pid) + (open-process-ports (str "pass find " (shell-quote (string-trim query)) " 2>/dev/null") + 'block (native-transcoder)))) + (close-port si) + (let loop ((lines '())) + (let ((line (get-line so))) + (if (eof-object? line) + (begin + (close-port so) (close-port se) + (echo-message! echo (string-join (reverse lines) " | "))) + (loop (cons (string-trim line) lines))))))))))) + (else (echo-message! echo (str "Unknown action: " act)))))))) --- a/src/jerboa-emacs/editor-extra-modes.ss +++ b/src/jerboa-emacs/editor-extra-modes.ss @@ -7165,3 +7165,348 @@ ;; Highlight the line (send-message ed SCI_INDICATORFILLRANGE line-start (- line-end line-start)) (echo-message! (app-state-echo app) "Line pulsed"))) + +;; ===== Round 18 Batch 1 ===== + +;; --- Feature 1: Insert Lorem Ipsum --- + +(def (cmd-insert-lorem-ipsum app) + "Insert Lorem Ipsum placeholder text." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (win (current-window frame)) + (ed (edit-window-editor win)) + (row (tui-rows)) (width (tui-cols)) + (paragraphs-str (echo-read-string echo "Paragraphs (default 3): " row width)) + (n (or (and paragraphs-str (not (string-empty? paragraphs-str)) + (string->number (string-trim paragraphs-str))) + 3)) + (lorem "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") + (extras '("Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris." + "Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus." + "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante." + "Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam."))) + (text (string-join + (let build ((i 0) (acc '())) + (if (>= i n) (reverse acc) + (build (+ i 1) + (cons (if (= i 0) lorem + (list-ref extras (modulo (- i 1) (length extras)))) + acc)))) + "\n\n"))) + (send-message ed SCI_INSERTTEXT -1 text) + (echo-message! echo (str "Inserted " n " paragraphs of Lorem Ipsum"))) + + +;; --- Feature 2: Generate Password --- + +(def (cmd-generate-password app) + "Generate a random password and insert it." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (win (current-window frame)) + (ed (edit-window-editor win)) + (row (tui-rows)) (width (tui-cols)) + (len-str (echo-read-string echo "Password length (default 16): " row width)) + (len (or (and len-str (not (string-empty? len-str)) + (string->number (string-trim len-str))) + 16)) + (chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;:,.<>?") + (password (list->string + (let gen ((i 0) (acc '())) + (if (>= i len) (reverse acc) + (gen (+ i 1) + (cons (string-ref chars (random (string-length chars))) acc))))))) + (send-message ed SCI_INSERTTEXT -1 password) + (echo-message! echo (str "Generated " len "-char password")))) + +;; --- Feature 3: Insert UUID --- + +(def (cmd-insert-uuid app) + "Insert a UUID (v4) at point." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (win (current-window frame)) + (ed (edit-window-editor win))) + (with-catch + (lambda (e) (echo-message! echo (str "UUID error: " e))) + (lambda () + (let-values (((si so se pid) + (open-process-ports + "python3 -c 'import uuid;print(uuid.uuid4())' 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null" + 'block (native-transcoder)))) + (close-port si) + (let ((uuid (get-line so))) + (close-port so) (close-port se) + (if (eof-object? uuid) + (echo-message! echo "Could not generate UUID") + (let ((id (string-trim uuid))) + (send-message ed SCI_INSERTTEXT -1 id) + (echo-message! echo (str "UUID: " id)))))))))) + +;; --- Feature 4: ASCII Art Text --- + +(def (cmd-ascii-art-text app) + "Convert text to ASCII art using figlet or toilet." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (win (current-window frame)) + (ed (edit-window-editor win)) + (row (tui-rows)) (width (tui-cols)) + (text (echo-read-string echo "Text for ASCII art: " row width))) + (when (and text (not (string-empty? text))) + (with-catch + (lambda (e) (echo-message! echo (str "figlet error: " e))) + (lambda () + (let-values (((si so se pid) + (open-process-ports + (str "figlet " (shell-quote (string-trim text)) + " 2>/dev/null || toilet " (shell-quote (string-trim text)) + " 2>/dev/null || echo " (shell-quote (string-trim text))) + 'block (native-transcoder)))) + (close-port si) + (let loop ((lines '())) + (let ((line (get-line so))) + (if (eof-object? line) + (begin + (close-port so) (close-port se) + (let ((art (string-join (reverse lines) "\n"))) + (send-message ed SCI_INSERTTEXT -1 art) + (echo-message! echo "ASCII art inserted"))) + (loop (cons line lines))))))))))) + +;; --- Feature 5: Matrix Effect --- + +(def (cmd-matrix-effect app) + "Display a Matrix-style rain animation in the buffer." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (new-buf (create-buffer "*matrix*"))) + (switch-to-buffer frame new-buf) + (let* ((ed (edit-window-editor (current-window frame))) + (width 80) (height 24) + (chars "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*") + (grid (make-vector (* width height) #\space))) + ;; Generate several frames of matrix rain + (let animate ((step 0)) + (when (< step 100) + ;; Drop random characters + (let ((col (random width))) + (let shift-down ((row (- height 1))) + (when (> row 0) + (vector-set! grid (+ (* row width) col) + (vector-ref grid (+ (* (- row 1) width) col))) + (shift-down (- row 1)))) + (vector-set! grid col + (string-ref chars (random (string-length chars))))) + (animate (+ step 1)))) + ;; Render final frame + (let* ((lines (let build-rows ((row 0) (acc '())) + (if (>= row height) (reverse acc) + (let ((line (list->string + (let build-cols ((col 0) (cs '())) + (if (>= col width) (reverse cs) + (build-cols (+ col 1) + (cons (vector-ref grid (+ (* row width) col)) cs))))))) + (build-rows (+ row 1) (cons line acc)))))) + (text (string-join lines "\n"))) + (editor-set-text ed text) + (echo-message! echo "Matrix loaded. Press undo to restore."))))) + +;; --- Feature 6: Game of Life --- + +(def (cmd-game-of-life app) + "Run Conway's Game of Life in the buffer." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (new-buf (create-buffer "*game-of-life*"))) + (switch-to-buffer frame new-buf) + (let* ((ed (edit-window-editor (current-window frame))) + (w 40) (h 20) + (grid (make-vector (* w h) 0))) + ;; Random initial state + (let init ((i 0)) + (when (< i (* w h)) + (vector-set! grid i (if (< (random 4) 1) 1 0)) + (init (+ i 1)))) + ;; Run a few generations + (let gen-loop ((gen 0)) + (when (< gen 50) + (let ((new-grid (make-vector (* w h) 0))) + (let row-loop ((y 0)) + (when (< y h) + (let col-loop ((x 0)) + (when (< x w) + (let* ((neighbors + (let count ((dy -1) (sum 0)) + (if (> dy 1) sum + (count (+ dy 1) + (+ sum (let count2 ((dx -1) (s2 0)) + (if (> dx 1) s2 + (count2 (+ dx 1) + (+ s2 (if (and (= dx 0) (= dy 0)) 0 + (let ((nx (modulo (+ x dx) w)) + (ny (modulo (+ y dy) h))) + (vector-ref grid (+ (* ny w) nx))))))))))))) + (alive (vector-ref grid (+ (* y w) x)))) + (vector-set! new-grid (+ (* y w) x) + (cond ((and (= alive 1) (or (= neighbors 2) (= neighbors 3))) 1) + ((and (= alive 0) (= neighbors 3)) 1) + (else 0)))) + (col-loop (+ x 1)))) + (row-loop (+ y 1)))) + ;; Copy new-grid to grid + (let cp ((i 0)) + (when (< i (* w h)) + (vector-set! grid i (vector-ref new-grid i)) + (cp (+ i 1))))) + (gen-loop (+ gen 1)))) + ;; Render + (let* ((lines (let build ((y 0) (acc '())) + (if (>= y h) (reverse acc) + (build (+ y 1) + (cons (list->string + (let bcol ((x 0) (cs '())) + (if (>= x w) (reverse cs) + (bcol (+ x 1) + (cons (if (= (vector-ref grid (+ (* y w) x)) 1) #\# #\.) cs))))) + acc))))) + (text (str "=== Game of Life (gen 50) ===\n\n" (string-join lines "\n") "\n"))) + (editor-set-text ed text) + (echo-message! echo "Game of Life rendered"))))) + +;; --- Feature 7: Mandelbrot --- + +(def (cmd-mandelbrot app) + "Render a text-based Mandelbrot set." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (new-buf (create-buffer "*mandelbrot*"))) + (switch-to-buffer frame new-buf) + (let* ((ed (edit-window-editor (current-window frame))) + (w 72) (h 24) + (chars " .:-=+*#%@") + (max-iter (- (string-length chars) 1)) + (lines + (let row-loop ((y 0) (acc '())) + (if (>= y h) (reverse acc) + (row-loop (+ y 1) + (cons (list->string + (let col-loop ((x 0) (cs '())) + (if (>= x w) (reverse cs) + (let* ((cr (+ -2.0 (* 3.0 (/ (inexact x) w)))) + (ci (+ -1.2 (* 2.4 (/ (inexact y) h))))) + (let iter ((zr 0.0) (zi 0.0) (i 0)) + (if (or (>= i max-iter) (> (+ (* zr zr) (* zi zi)) 4.0)) + (col-loop (+ x 1) (cons (string-ref chars i) cs)) + (iter (+ (- (* zr zr) (* zi zi)) cr) + (+ (* 2.0 zr zi) ci) + (+ i 1)))))))) + acc))))) + (text (str "=== Mandelbrot Set ===\n\n" (string-join lines "\n") "\n"))) + (editor-set-text ed text) + (echo-message! echo "Mandelbrot set rendered")))) + +;; --- Feature 8: Maze Generator --- + +(def (cmd-maze-generator app) + "Generate a random maze in the buffer." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (new-buf (create-buffer "*maze*"))) + (switch-to-buffer frame new-buf) + (let* ((ed (edit-window-editor (current-window frame))) + (w 21) (h 11) ;; Must be odd + (grid (make-vector (* w h) #\#))) + ;; Simple recursive backtracker (iterative version with stack) + (let ((start-x 1) (start-y 1)) + (vector-set! grid (+ (* start-y w) start-x) #\space) + (let carve ((stack (list (cons start-x start-y)))) + (when (not (null? stack)) + (let* ((pos (car stack)) + (cx (car pos)) (cy (cdr pos)) + (dirs '((0 . -2) (0 . 2) (-2 . 0) (2 . 0))) + ;; Shuffle directions + (shuffled (let shuffle ((d dirs) (acc '())) + (if (null? d) acc + (let ((idx (random (length d)))) + (shuffle (append (take d idx) (drop d (+ idx 1))) + (cons (list-ref d idx) acc))))))) + (let try-dirs ((ds shuffled) (moved #f)) + (if (or moved (null? ds)) + (if moved + (carve stack) + (carve (cdr stack))) + (let* ((dx (caar ds)) (dy (cdar ds)) + (nx (+ cx dx)) (ny (+ cy dy))) + (if (and (> nx 0) (< nx (- w 1)) + (> ny 0) (< ny (- h 1)) + (char=? (vector-ref grid (+ (* ny w) nx)) #\#)) + (begin + ;; Carve wall between + (vector-set! grid (+ (* (+ cy (quotient dy 2)) w) (+ cx (quotient dx 2))) #\space) + (vector-set! grid (+ (* ny w) nx) #\space) + (try-dirs (cdr ds) #t) + (carve (cons (cons nx ny) stack))) + (try-dirs (cdr ds) moved))))))))) + ;; Render + (let* ((lines (let build ((y 0) (acc '())) + (if (>= y h) (reverse acc) + (build (+ y 1) + (cons (list->string + (let bcol ((x 0) (cs '())) + (if (>= x w) (reverse cs) + (bcol (+ x 1) (cons (vector-ref grid (+ (* y w) x)) cs))))) + acc))))) + (text (str "=== Random Maze ===\n\n" (string-join lines "\n") "\n"))) + (editor-set-text ed text) + (echo-message! echo "Maze generated"))))) + +;; --- Feature 9: Typing Speed Test --- + +(def (cmd-typing-speed-test app) + "Test your typing speed (WPM)." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (phrases '("the quick brown fox jumps over the lazy dog" + "pack my box with five dozen liquor jugs" + "how vexingly quick daft zebras jump" + "the five boxing wizards jump quickly" + "sphinx of black quartz judge my vow")) + (phrase (list-ref phrases (random (length phrases)))) + (new-buf (create-buffer "*typing-test*"))) + (switch-to-buffer frame new-buf) + (let ((ed (edit-window-editor (current-window frame)))) + (editor-set-text ed (str "=== Typing Speed Test ===\n\n" + "Type the following phrase:\n\n" + " " phrase "\n\n" + "When ready, use M-x typing-speed-submit to check your result.\n")) + (echo-message! echo (str "Type: " phrase))))) + +;; --- Feature 10: Pomodoro Timer --- + +(def (cmd-pomodoro-timer app) + "Start a Pomodoro timer (25-minute work session)." + (let* ((echo (app-state-echo app)) + (frame (app-state-frame app)) + (row (tui-rows)) (width (tui-cols)) + (duration-str (echo-read-string echo "Minutes (default 25): " row width)) + (minutes (or (and duration-str (not (string-empty? duration-str)) + (string->number (string-trim duration-str))) + 25)) + (new-buf (create-buffer "*pomodoro*"))) + (switch-to-buffer frame new-buf) + (let ((ed (edit-window-editor (current-window frame)))) + (editor-set-text ed (str "=== Pomodoro Timer ===\n\n" + "Duration: " minutes " minutes\n" + "Started at: " (with-output-to-string + (lambda () + (let-values (((si so se pid) + (open-process-ports "date '+%H:%M:%S'" 'block (native-transcoder)))) + (close-port si) + (let ((t (get-line so))) + (close-port so) (close-port se) + (when (not (eof-object? t)) (display (string-trim t))))))) + "\n\nFocus on your work!\n" + "Use M-x pomodoro-check to see remaining time.\n")) + (echo-message! echo (str "Pomodoro started: " minutes " minutes"))))) --- a/src/jerboa-emacs/editor-extra-regs2.ss +++ b/src/jerboa-emacs/editor-extra-regs2.ss @@ -1813,4 +1813,26 @@ (register-command! 'locate-library cmd-locate-library) (register-command! 'load-library cmd-load-library) (register-command! 'finder-by-keyword cmd-finder-by-keyword) + ;; Round 18 batch 1: insert-lorem-ipsum, generate-password, insert-uuid, ascii-art-text, matrix-effect, game-of-life, mandelbrot, maze-generator, typing-speed-test, pomodoro-timer + (register-command! 'insert-lorem-ipsum cmd-insert-lorem-ipsum) + (register-command! 'generate-password cmd-generate-password) + (register-command! 'insert-uuid cmd-insert-uuid) + (register-command! 'ascii-art-text cmd-ascii-art-text) + (register-command! 'matrix-effect cmd-matrix-effect) + (register-command! 'game-of-life cmd-game-of-life) + (register-command! 'mandelbrot cmd-mandelbrot) + (register-command! 'maze-generator cmd-maze-generator) + (register-command! 'typing-speed-test cmd-typing-speed-test) + (register-command! 'pomodoro-timer cmd-pomodoro-timer) + ;; Round 18 batch 2: stopwatch, countdown-timer, snow-effect, hangman, image-to-ascii, buffer-menu, fire-effect, lolcat, toggle-narrow-to-region, password-store + (register-command! 'stopwatch cmd-stopwatch) + (register-command! 'countdown-timer cmd-countdown-timer) + (register-command! 'snow-effect cmd-snow-effect) + (register-command! 'hangman cmd-hangman) + (register-command! 'image-to-ascii cmd-image-to-ascii) + (register-command! 'buffer-menu cmd-buffer-menu) + (register-command! 'fire-effect cmd-fire-effect) + (register-command! 'lolcat cmd-lolcat) + (register-command! 'toggle-narrow-to-region cmd-toggle-narrow-to-region) + (register-command! 'password-store cmd-password-store) )