Yeah, these posts about vibe coding are a bit tedious, but I got an interesting result. My hypothesis about vibe coding in Common Lisp was that there is simply not enough training data. But the most popular lisp is not Common Lisp but Emacs Lisp. It should have enough training data. So I tried vibe coding an application in Emacs Lisp. Again I used minesweeper as the example.
The model was Claude 3.7 Sonnet and I used VSCode in "Agent" mode. It generated a "minesweeper.el" file with 455 lines of code. There were some errors when I tried to run it, so I simply copied the error message into the AI prompt and let the VSCode agent fix it. After a couple of iterations, the code seems to work. It isn't the most polished minsweeper clone, but it is sort of a serviceable game.
So I guess the conclusion is that vibe coding works in Emacs Lisp. The result isn't all that slick, but what do expect from an Emacs game? This tends to support the hypothesis that you need ample training data.
Here is the code:
;;; minesweeper.el --- A Minesweeper game for Emacs -*- lexical-binding: t -*- ;; Author: AI Assistant ;; Keywords: games ;; Version: 1.0 ;; Package-Requires: ((emacs "24.3")) ;;; Commentary: ;; This package implements the classic Minesweeper game for Emacs. ;; Use M-x minesweeper to start the game. ;;; Code: (require 'cl-lib) ;;; Constants and Variables (defvar minesweeper-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "<down>") 'minesweeper-move-down) (define-key map (kbd "<up>") 'minesweeper-move-up) (define-key map (kbd "<left>") 'minesweeper-move-left) (define-key map (kbd "<right>") 'minesweeper-move-right) (define-key map (kbd "RET") 'minesweeper-reveal) (define-key map (kbd "SPC") 'minesweeper-flag) (define-key map (kbd "r") 'minesweeper-restart) (define-key map (kbd "q") 'minesweeper-quit) map) "Keymap for ‘minesweeper-mode'.") (defgroup minesweeper nil "Minesweeper game for Emacs." :group 'games) (defcustom minesweeper-board-width 10 "Width of the Minesweeper game board." :type 'integer :group 'minesweeper) (defcustom minesweeper-board-height 10 "Height of the Minesweeper game board." :type 'integer :group 'minesweeper) (defcustom minesweeper-mine-count 15 "Number of mines on the Minesweeper board." :type 'integer :group 'minesweeper) (defface minesweeper-hidden-face '((t :background "gray80" :box (:line-width -1 :color "gray50"))) "Face for hidden cells." :group 'minesweeper) (defface minesweeper-flag-face '((t :background "gray80" :foreground "red" :weight bold :box (:line-width -1 :color "gray50"))) "Face for flagged cells." :group 'minesweeper) (defface minesweeper-mine-face '((t :background "red" :foreground "black" :weight bold :box (:line-width -1 :color "gray50"))) "Face for mine cells." :group 'minesweeper) (defface minesweeper-empty-face '((t :background "gray95" :box (:line-width -1 :color "gray50"))) "Face for empty revealed cells." :group 'minesweeper) (defface minesweeper-number-1-face '((t :background "gray95" :foreground "blue" :weight bold :box (:line-width -1 :color "gray50"))) "Face for cells with 1 adjacent mine." :group 'minesweeper) (defface minesweeper-number-2-face '((t :background "gray95" :foreground "green" :weight bold :box (:line-width -1 :color "gray50"))) "Face for cells with 2 adjacent mines." :group 'minesweeper) (defface minesweeper-number-3-face '((t :background "gray95" :foreground "red" :weight bold :box (:line-width -1 :color "gray50"))) "Face for cells with 3 adjacent mines." :group 'minesweeper) (defface minesweeper-number-4-face '((t :background "gray95" :foreground "purple" :weight bold :box (:line-width -1 :color "gray50"))) "Face for cells with 4 adjacent mines." :group 'minesweeper) (defface minesweeper-number-5-face '((t :background "gray95" :foreground "maroon" :weight bold :box (:line-width -1 :color "gray50"))) "Face for cells with 5 adjacent mines." :group 'minesweeper) (defface minesweeper-number-6-face '((t :background "gray95" :foreground "turquoise" :weight bold :box (:line-width -1 :color "gray50"))) "Face for cells with 6 adjacent mines." :group 'minesweeper) (defface minesweeper-number-7-face '((t :background "gray95" :foreground "black" :weight bold :box (:line-width -1 :color "gray50"))) "Face for cells with 7 adjacent mines." :group 'minesweeper) (defface minesweeper-number-8-face '((t :background "gray95" :foreground "gray50" :weight bold :box (:line-width -1 :color "gray50"))) "Face for cells with 8 adjacent mines." :group 'minesweeper) (defvar minesweeper-buffer-name "*Minesweeper*" "Name of the Minesweeper game buffer.") (defvar minesweeper-board nil "The game board. Each cell is a list of the form (MINE-P REVEALED-P FLAGGED-P MINE-COUNT).") (defvar minesweeper-game-over nil "Whether the current game is over.") (defvar minesweeper-game-won nil "Whether the current game is won.") (defvar minesweeper-flags-placed 0 "Number of flags placed on the board.") (defvar minesweeper-current-pos '(0 . 0) "Current cursor position as (ROW . COL).") ;;; Game Functions (defun minesweeper-init-board () "Initialize the game board." (setq minesweeper-board (make-vector minesweeper-board-height nil)) (let ((board-cells (* minesweeper-board-width minesweeper-board-height)) (mine-positions (make-vector (* minesweeper-board-width minesweeper-board-height) nil))) ;; Initialize all cells (dotimes (row minesweeper-board-height) (let ((row-vec (make-vector minesweeper-board-width nil))) (dotimes (col minesweeper-board-width) (aset row-vec col (list nil nil nil 0))) ; (mine-p revealed-p flagged-p mine-count) (aset minesweeper-board row row-vec))) ;; Randomly place mines (dotimes (i minesweeper-mine-count) (let ((pos (random board-cells))) (while (aref mine-positions pos) (setq pos (random board-cells))) (aset mine-positions pos t) (let* ((row (/ pos minesweeper-board-width)) (col (% pos minesweeper-board-width)) (cell (aref (aref minesweeper-board row) col))) (setcar cell t)))) ; Set mine-p to t ;; Calculate adjacent mine counts (dotimes (row minesweeper-board-height) (dotimes (col minesweeper-board-width) (unless (car (aref (aref minesweeper-board row) col)) ; Skip if it's a mine (let ((count 0)) (dolist (r (list -1 0 1)) (dolist (c (list -1 0 1)) (unless (and (= r 0) (= c 0)) (let ((new-row (+ row r)) (new-col (+ col c))) (when (and (>= new-row 0) (< new-row minesweeper-board-height) (>= new-col 0) (< new-col minesweeper-board-width)) (when (car (aref (aref minesweeper-board new-row) new-col)) (setq count (1+ count)))))))) (setcar (nthcdr 3 (aref (aref minesweeper-board row) col)) count)))))) (setq minesweeper-game-over nil minesweeper-game-won nil minesweeper-flags-placed 0 minesweeper-current-pos '(0 . 0))) (defun minesweeper-get-cell (row col) "Get the cell at ROW and COL." (aref (aref minesweeper-board row) col)) (cl-defun minesweeper-reveal (row col) "Reveal the cell at ROW and COL." (interactive (if current-prefix-arg (list (read-number "Row: ") (read-number "Column: ")) (list (car minesweeper-current-pos) (cdr minesweeper-current-pos)))) (when minesweeper-game-over (message "Game over. Press 'r' to restart.") (cl-return-from minesweeper-reveal nil)) (let* ((cell (minesweeper-get-cell row col)) (mine-p (nth 0 cell)) (revealed-p (nth 1 cell)) (flagged-p (nth 2 cell)) (mine-count (nth 3 cell))) (when flagged-p (cl-return-from minesweeper-reveal nil)) (when revealed-p (cl-return-from minesweeper-reveal nil)) (setcar (nthcdr 1 cell) t) ; Set revealed-p to t (if mine-p (progn (setq minesweeper-game-over t) (minesweeper-reveal-all-mines) (minesweeper-draw-board) (message "BOOM! Game over.")) ;; Reveal adjacent cells if this is an empty cell (when (= mine-count 0) (dolist (r (list -1 0 1)) (dolist (c (list -1 0 1)) (unless (and (= r 0) (= c 0)) (let ((new-row (+ row r)) (new-col (+ col c))) (when (and (>= new-row 0) (< new-row minesweeper-board-height) (>= new-col 0) (< new-col minesweeper-board-width)) (minesweeper-reveal new-row new-col))))))) (minesweeper-check-win))) (minesweeper-draw-board)) (cl-defun minesweeper-flag (row col) "Toggle flag on cell at ROW and COL." (interactive (if current-prefix-arg (list (read-number "Row: ") (read-number "Column: ")) (list (car minesweeper-current-pos) (cdr minesweeper-current-pos)))) (when minesweeper-game-over (message "Game over. Press 'r' to restart.") (cl-return-from minesweeper-flag nil)) (let* ((cell (minesweeper-get-cell row col)) (revealed-p (nth 1 cell)) (flagged-p (nth 2 cell))) (when revealed-p (cl-return-from minesweeper-flag nil)) (if flagged-p (progn (setcar (nthcdr 2 cell) nil) ; Remove flag (setq minesweeper-flags-placed (1- minesweeper-flags-placed))) (setcar (nthcdr 2 cell) t) ; Add flag (setq minesweeper-flags-placed (1+ minesweeper-flags-placed)))) (minesweeper-draw-board)) (defun minesweeper-reveal-all-mines () "Reveal all mines on the board." (dotimes (row minesweeper-board-height) (dotimes (col minesweeper-board-width) (let* ((cell (minesweeper-get-cell row col)) (mine-p (nth 0 cell))) (when mine-p (setcar (nthcdr 1 cell) t)))))) ; Set revealed-p to t (defun minesweeper-check-win () "Check if the game is won." (let ((all-non-mines-revealed t)) (dotimes (row minesweeper-board-height) (dotimes (col minesweeper-board-width) (let* ((cell (minesweeper-get-cell row col)) (mine-p (nth 0 cell)) (revealed-p (nth 1 cell))) (when (and (not mine-p) (not revealed-p)) (setq all-non-mines-revealed nil))))) (when all-non-mines-revealed (setq minesweeper-game-over t minesweeper-game-won t) (message "You win!") (minesweeper-flag-all-mines)))) (defun minesweeper-flag-all-mines () "Flag all mines on the board." (dotimes (row minesweeper-board-height) (dotimes (col minesweeper-board-width) (let* ((cell (minesweeper-get-cell row col)) (mine-p (nth 0 cell)) (flagged-p (nth 2 cell))) (when (and mine-p (not flagged-p)) (setcar (nthcdr 2 cell) t)))))) ;;; UI Functions (defun minesweeper-draw-cell (row col) "Draw the cell at ROW and COL." (let* ((cell (minesweeper-get-cell row col)) (mine-p (nth 0 cell)) (revealed-p (nth 1 cell)) (flagged-p (nth 2 cell)) (mine-count (nth 3 cell)) (char " ") (face 'minesweeper-hidden-face) (current-p (and (= row (car minesweeper-current-pos)) (= col (cdr minesweeper-current-pos))))) (cond (flagged-p (setq char "F") (setq face 'minesweeper-flag-face)) (revealed-p (cond (mine-p (setq char "*") (setq face 'minesweeper-mine-face)) ((= mine-count 0) (setq char " ") (setq face 'minesweeper-empty-face)) (t (setq char (number-to-string mine-count)) (setq face (intern (format "minesweeper-number-%d-face" mine-count)))))) (t (setq char " ") (setq face 'minesweeper-hidden-face))) (insert (propertize char 'face face)) (when current-p (put-text-property (1- (point)) (point) 'cursor t)))) (defun minesweeper-draw-board () "Draw the game board." (let ((inhibit-read-only t) (old-point (point))) (erase-buffer) ;; Draw header (insert (format "Minesweeper: %d mines, %d flags placed\n\n" minesweeper-mine-count minesweeper-flags-placed)) ;; Draw column numbers (insert " ") (dotimes (col minesweeper-board-width) (insert (format "%d" (% col 10)))) (insert "\n") ;; Draw top border (insert " ") (dotimes (col minesweeper-board-width) (insert "-")) (insert "\n") ;; Draw board rows (dotimes (row minesweeper-board-height) (insert (format "%d|" (% row 10))) (dotimes (col minesweeper-board-width) (minesweeper-draw-cell row col)) (insert "|\n")) ;; Draw bottom border (insert " ") (dotimes (col minesweeper-board-width) (insert "-")) (insert "\n\n") ;; Draw status (cond (minesweeper-game-won (insert "You won! Press 'r' to restart or 'q' to quit.")) (minesweeper-game-over (insert "Game over! Press 'r' to restart or 'q' to quit.")) (t (insert "Press 'r' to restart, 'q' to quit.\n") (insert "Use arrows to move, ENTER to reveal, SPACE to toggle flag."))) ;; Restore point or set to position of cursor (goto-char (point-min)) (let ((result (text-property-search-forward 'cursor))) (if result (goto-char (prop-match-beginning result)) (goto-char old-point))))) ;;; Movement Functions (defun minesweeper-move-up () "Move cursor up." (interactive) (let ((row (car minesweeper-current-pos)) (col (cdr minesweeper-current-pos))) (when (> row 0) (setq minesweeper-current-pos (cons (1- row) col)) (minesweeper-draw-board)))) (defun minesweeper-move-down () "Move cursor down." (interactive) (let ((row (car minesweeper-current-pos)) (col (cdr minesweeper-current-pos))) (when (< row (1- minesweeper-board-height)) (setq minesweeper-current-pos (cons (1+ row) col)) (minesweeper-draw-board)))) (defun minesweeper-move-left () "Move cursor left." (interactive) (let ((row (car minesweeper-current-pos)) (col (cdr minesweeper-current-pos))) (when (> col 0) (setq minesweeper-current-pos (cons row (1- col))) (minesweeper-draw-board)))) (defun minesweeper-move-right () "Move cursor right." (interactive) (let ((row (car minesweeper-current-pos)) (col (cdr minesweeper-current-pos))) (when (< col (1- minesweeper-board-width)) (setq minesweeper-current-pos (cons row (1+ col))) (minesweeper-draw-board)))) ;;; Game Management Functions (defun minesweeper-restart () "Restart the Minesweeper game." (interactive) (minesweeper-init-board) (minesweeper-draw-board)) (defun minesweeper-quit () "Quit the Minesweeper game." (interactive) (kill-buffer minesweeper-buffer-name)) (define-derived-mode minesweeper-mode special-mode "Minesweeper" "Major mode for playing Minesweeper." (setq buffer-read-only t) (setq-local cursor-type nil) (buffer-disable-undo)) ;;;###autoload (defun minesweeper () "Start a new game of Minesweeper." (interactive) (switch-to-buffer minesweeper-buffer-name) (minesweeper-mode) (minesweeper-init-board) (minesweeper-draw-board)) (provide 'minesweeper) ;;; minesweeper.el ends here
To run it, you can save the code to a file named "minesweeper.el"
and load it in Emacs with M-x load-file
. Then start the game
with M-x minesweeper
.