From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Tino Calancha Newsgroups: gmane.emacs.devel Subject: Minesweeper game for Emacs Date: Mon, 6 Nov 2017 21:10:14 +0900 (JST) Message-ID: NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; BOUNDARY="8323329-1831888515-1509970217=:17869" X-Trace: blaine.gmane.org 1509979307 23924 195.159.176.226 (6 Nov 2017 14:41:47 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Mon, 6 Nov 2017 14:41:47 +0000 (UTC) User-Agent: Alpine 2.20 (DEB 67 2015-01-07) Cc: Tino Calancha To: Emacs developers Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Mon Nov 06 15:41:39 2017 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eBian-0005m3-0F for ged-emacs-devel@m.gmane.org; Mon, 06 Nov 2017 15:41:37 +0100 Original-Received: from localhost ([::1]:48514 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eBiar-00067C-5V for ged-emacs-devel@m.gmane.org; Mon, 06 Nov 2017 09:41:41 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:46891) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eBgES-0004Ev-BA for emacs-devel@gnu.org; Mon, 06 Nov 2017 07:10:27 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1eBgEP-00016b-3O for emacs-devel@gnu.org; Mon, 06 Nov 2017 07:10:24 -0500 Original-Received: from mail-pf0-x22c.google.com ([2607:f8b0:400e:c00::22c]:53446) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1eBgEO-00015j-Mg for emacs-devel@gnu.org; Mon, 06 Nov 2017 07:10:21 -0500 Original-Received: by mail-pf0-x22c.google.com with SMTP id t188so7583317pfd.10 for ; Mon, 06 Nov 2017 04:10:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:date:to:cc:subject:message-id:user-agent:mime-version; bh=eITXZ5H7Fj1Qk+xtTRFWM7zjbPGAcwwkramH8s7XRts=; b=FknxtEbGiPRRuxYjqUTDX+EGBnnzK2PZB3jmKhtYIYNofjJuV6xm/5aHX4ftkb/ADy jDGhj/zeS2SAYkhmmEEzVylwMAt0cZItYKB7ubkWQVrYpg6/iv8Ml5XKF4b32jP/B7hp Z56OD57myGDN+jENxCTHKxZoUQPseYXTXGXAc62Tn4QEZ1YuWQf8LZ4jmteIosPBzaBA y4wxVugpbkas67DBThWWmCysupa8Pri/VsHj3CRGSD7gRPw7YeacTlyDTSdNJqZotIn3 dfCKt575s1elRr6f2pAwsL6tO5rLLUVYKZywjyQC8kC0D2bNtplizbFJLLFw2hVTzuqk G3eA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:date:to:cc:subject:message-id:user-agent :mime-version; bh=eITXZ5H7Fj1Qk+xtTRFWM7zjbPGAcwwkramH8s7XRts=; b=fBfJ6ycwA0zoN8ft19fEW3PmBjN7bL6ujFX0uYcOEkBeLo03gCqtYaRONRyx3rCLgY jQgD7ivqrrmdg/b2RUOX+gJQc8OtEcH3XLQuNhlMk/8/VJ+st8F+9CY+20pSfyRzUsZN Np91Dpfletm1YUi0y6dbCQJ/rwSKhOvJCzgtekPl4hAhAGRXLkLZyCnlDE2KNmnHHstt 2rml8CnFVZ9WKX4PnaCEqPmdOCwtR5I1tVOJDLgqRIOliOymC8K7rsx9qemybBmj1eqn Ebn2ID+USgREA4xCvLahk8VB5s+HldwAvppRi63TOiTt5Df4D8Y8umoSHvndXMphcHAq Wmww== X-Gm-Message-State: AMCzsaUGgUexeSNqNaha7OwYCRXhaDfWs1te55+l0zhILmqXoQIfx+wL sbMP755TT/evwI1q+oOGHnylVw== X-Google-Smtp-Source: ABhQp+SYrz34x+ydbR3pYRo3Mcw5q3rZ8hENRT/k2KJLIH8EdlkMajfknGip22RnU/8KzfTXYA8yEA== X-Received: by 10.99.180.3 with SMTP id s3mr14749921pgf.174.1509970218866; Mon, 06 Nov 2017 04:10:18 -0800 (PST) Original-Received: from calancha-pc (203.191.218.133.dy.bbexcite.jp. [133.218.191.203]) by smtp.gmail.com with ESMTPSA id y127sm20383267pgb.52.2017.11.06.04.10.16 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 06 Nov 2017 04:10:18 -0800 (PST) X-Google-Original-From: Tino Calancha X-X-Sender: calancha@calancha-pc X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 2607:f8b0:400e:c00::22c X-Mailman-Approved-At: Mon, 06 Nov 2017 09:41:03 -0500 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:219947 Archived-At: --8323329-1831888515-1509970217=:17869 Content-Type: text/plain; format=flowed; charset=UTF-8 Content-Transfer-Encoding: 8BIT Hi, this is an elisp version of the classical minesweeper game. Is of any interest to include this in Emacs? Or should go to ELPA instead? Thank you Tino --8<-----------------------------cut here---------------start------------->8--- ;;; mines.el --- Minesweeper game -*- lexical-binding: t -*- ;; Copyright (C) 2017 Tino Calancha ;; Author: Tino Calancha ;; Maintainer: Tino Calancha ;; Created: 2017-10-28 ;; Keywords: games ;; Version: 1.0 ;; Package-Requires: ((emacs "24.3") (cl-lib "0.5")) ;; url: https://github.com/calancha/Minesweeper ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This is an elisp implementation of the classical minesweeper game. ;; The target is localize all hidden mines (bombs) in a rectangular board ;; without detonating them. You reveal the content of a cell with the ;; command `mines-dig'. ;; ;; 1. Cells with a bomb contain the character 'x'; if you call `mines-dig' ;; in these cells then you lost the game. ;; 2. Cells without bomb at distance 1 from any cell with a mine ;; contain a number: the number of bombs at distance 1 from this cell. ;; If you reveal the content of this cell, then this number is shown. ;; ;; 3. Cells without a bomb at distance > 1 from any bomb contain '@'. ;; If you reveal the content of this cell, then '@' is shown and ;; all adjacent cells are recursively revealed. ;; ;; ;; If you think that an uncovered cell has a mine, you might flag it ;; with `mines-flag-cell'; if you call this command again in the same ;; cell the cell is unflagged. This is useful to visualize your ;; progress in the game. ;; ;; The game is completed once all mine-free cells are revealed, that is, ;; when the only uncovered cells equals the number of hidden mines. ;; ;;; Code: (require 'gamegrid) (require 'cl-lib) (require 'cookie1) ; For `cookie-shuffle-vector'. ;;; Internal variables. (defgroup mines nil "Play minessweeper." :group 'games :prefix "mines-") (defcustom mines-protect-first-move t "Non-nil avoid game over in the first cell revealed." :type 'boolean :version "27.1") (defcustom mines-mode-hook nil "Hook run by mines mode." :type 'hook :group 'mines :version "27.1") (defvar mines-uncover-cell-char ?. "Char to display uncover cells.") (defvar mines-flagged-cell-char ?! "Char to display flagged cells as maybe having a mine.") (defvar mines-empty-cell-char ?@ "Char to display a cell without mine nor numbers.") (defvar mines-empty-cell-mine ?x "Char to display a cell with a mine.") (defvar mines-buffer nil "Buffer where play minesweeper.") (defvar mines-start-pos 2 "Initial prompt position.") (defvar mines-number-mines 10 "Number of mines.") (defvar mines-number-rows 8 "Nmber of rows.") (defvar mines-number-cols 8 "Number of columns.") (defvar mines-number-cells (* mines-number-rows mines-number-cols) "Number of cells.") (defcustom mines-difficulty-level 'easy "Level of difficulty. If `easy' we have 8 columns x 8 columns and 10 mines. If `medium' we have 16 columns x 16 columns and 40 mines. If `hard' we have 30 columns x 16 columns and 99 mines. If `custom' then ask user for these numbers." :type '(choice (const :tag "Easy" easy) (const :tag "Medium" medium) (const :tag "Hard" hard) (const :tag "Custom" custom)) :group 'games :set (lambda (sym val) (if (not (eq val 'custom)) (set sym val) (setq mines-number-cols (read-number "Number of columns: ") mines-number-rows (read-number "Number of rows: ") mines-number-mines (read-number "Number of mines: ")) (set sym val))) :version "27.1") (defvar mines-grid (make-vector mines-number-cells nil) "Game configuration.") (defvar mines-state (make-vector mines-number-cells nil) "Game state.") (defvar mines-mine-positions nil "Mine positions.") (defvar mines-gap-positions nil "Empty cell positions.") (defvar mines-init-time nil "Initial time of the game.") (defvar mines-end-time nil "End time of the game.") (defvar mines-undone-neighbours nil "List of uncovered neighbours for the current cell.") (defvar-local mines-game-over nil "Non-nil if the game in current buffer has ended.") (defmacro mines-init (cond1 cond2 cond3 cond4 &rest body) (declare (debug (form form form form &rest body))) `(progn (cond (,cond1 (setq mines-number-cols 8 mines-number-rows 8 mines-number-mines 10 mines-difficulty-level 'easy)) (,cond2 (setq mines-number-cols 16 mines-number-rows 16 mines-number-mines 40 mines-difficulty-level 'medium)) (,cond3 (setq mines-number-cols 30 mines-number-rows 16 mines-number-mines 99 mines-difficulty-level 'hard)) (,cond4 (setq mines-difficulty-level 'custom) ,@body)) (setq mines-number-cells (* mines-number-rows mines-number-cols)))) ;;; Moving. (defun mines-index-2-matrix (idx) "Translate 1-D array index into 2-D matrix indices." (let* ((col (% idx mines-number-cols)) (row (/ idx mines-number-cols))) (list row col))) (defun mines-matrix-2-index (row col) "Translate 2-D matrix indices into 1-D array index." (+ col (* row mines-number-cols))) (defun mines-get-neighbours (idx) "Return cell neighbour indices for cell at IDX." (let* ((row-col (mines-index-2-matrix idx)) (row (car row-col)) (col (cadr row-col)) res) (cl-flet ((add-fn (to-row) (or (= row to-row) (push (list to-row col) res)) (and (< col (1- mines-number-cols)) (push (list to-row (1+ col)) res)) (or (zerop col) (push (list to-row (1- col)) res)))) (progn (add-fn row) ; Horizontal neighburs. (unless (zerop row) (add-fn (1- row))) ; Up neighbours. (when (< row (1- mines-number-rows)) ; Below neighbours. (add-fn (1+ row))))) (mapcar (lambda (x) (mines-matrix-2-index (car x) (cadr x))) res))) (defun mines-goto (idx) "Move to cell at IDX." (goto-char 1) (let ((cidx (mines-current-pos))) (ignore-errors (while (not (= cidx idx)) (goto-char (next-single-property-change (point) 'idx)) (setq cidx (mines-current-pos))) (goto-char (1+ (point)))))) (defun mines-go-right () "Move 1 cell to the right." (interactive) (if (= (point) (point-max)) (progn (forward-line -1) (goto-char (1+ (point)))) (let* ((idx (mines-current-pos)) (row-col (mines-index-2-matrix idx)) (row (car row-col)) (col (cadr row-col))) (if (= col (1- mines-number-cols)) (mines-goto (apply #'mines-matrix-2-index (list row 0))) (mines-goto (1+ (mines-current-pos))))))) (defun mines-go-left () "Move 1 cell to the left." (interactive) (if (= (point) (point-max)) (goto-char (1- (point))) (let* ((idx (mines-current-pos)) (row-col (mines-index-2-matrix idx)) (row (car row-col)) (col (cadr row-col))) (if (zerop col) (mines-goto (apply #'mines-matrix-2-index (list row (1- mines-number-cols)))) (mines-goto (1- (mines-current-pos))))))) (defun mines-go-down () "Move to the cell under the current one." (interactive) (if (= (point) (point-max)) (goto-char mines-start-pos) (let* ((idx (mines-current-pos)) (row-col (mines-index-2-matrix idx)) (row (car row-col)) (col (cadr row-col))) (if (= row (1- mines-number-rows)) (mines-goto (apply #'mines-matrix-2-index (list 0 col))) (mines-goto (apply #'mines-matrix-2-index (list (1+ row) col))))))) (defun mines-go-up () "Move to the cell over the current one." (interactive) (if (= (point) (point-max)) (progn (forward-line -1) (goto-char (1+ (point)))) (let* ((idx (mines-current-pos)) (row-col (mines-index-2-matrix idx)) (row (car row-col)) (col (cadr row-col))) (if (zerop row) (mines-goto (apply #'mines-matrix-2-index (list (1- mines-number-rows) col))) (mines-goto (apply #'mines-matrix-2-index (list (1- row) col))))))) ;;; Main Functions. (defun mines--find-pos (elt vec) (let ((pos 0) res) (while (setq pos (cl-position-if (lambda (x) (cond ((null elt) ;; Check if the cell is empty or flagged. (or (null x) (eq mines-flagged-cell-char x))) (t (eq elt x)))) vec :start pos)) (push pos res) (cl-incf pos)) (nreverse res))) (defun mines-start () "Set mine positions for a new game." ;; Erase vector. (setq mines-grid (make-vector mines-number-cells nil)) (setq mines-state (make-vector mines-number-cells nil)) (let ((numbers (append (cookie-shuffle-vector (vconcat (number-sequence 0 (1- mines-number-cells)))) nil))) (dotimes (_ mines-number-mines) (aset mines-grid (pop numbers) t)) (setq mines-mine-positions (mines--find-pos t mines-grid)))) (defun mines--near-bombs (i j) (let ((numb 0)) ;; Horizontal neighbours. (when (> j 0) (and (aref mines-grid (mines-matrix-2-index i (1- j))) (cl-incf numb))) (when (< j (1- mines-number-cols)) (and (aref mines-grid (mines-matrix-2-index i (1+ j))) (cl-incf numb))) ;; Previous row neighbours. (when (> i 0) (and (aref mines-grid (mines-matrix-2-index (1- i) j)) (cl-incf numb)) (when (> j 0) (and (aref mines-grid (mines-matrix-2-index (1- i) (1- j))) (cl-incf numb))) (when (< j (1- mines-number-cols)) (and (aref mines-grid (mines-matrix-2-index (1- i) (1+ j))) (cl-incf numb)))) ;; Next row neighbours. (when (< i (1- mines-number-rows)) (and (aref mines-grid (mines-matrix-2-index (1+ i) j)) (cl-incf numb)) (when (> j 0) (and (aref mines-grid (mines-matrix-2-index (1+ i) (1- j))) (cl-incf numb))) (when (< j (1- mines-number-cols)) (and (aref mines-grid (mines-matrix-2-index (1+ i) (1+ j))) (cl-incf numb)))) numb)) (defun mines-set-numbers () "Set numbers for cells adjacent to cells with bombs." (let ((tmp-grid (copy-sequence mines-grid))) (dotimes (i mines-number-rows) (dotimes (j mines-number-cols) (let ((idx (mines-matrix-2-index i j))) (unless (aref mines-grid idx) (let ((numb (mines--near-bombs i j))) (unless (zerop numb) (aset tmp-grid idx numb))))))) (setq mines-grid tmp-grid))) (defun mines-list-game-conditions () "Return number of rows, columns and mines for current game." (interactive) (when (mines-mines-mode-p) (let ((rows mines-number-rows) (cols mines-number-cols) (mines mines-number-mines)) (message "%d rows x %d columns with %d mines" rows cols mines) (list rows cols mines)))) (defun mines--insert (elt idx &optional props null-str flag-or-unflag) (let* ((face nil) (str (cond ((null elt) (if (null null-str) (format " %c " mines-uncover-cell-char) ;; Uncover all its uncovered neighbours. (save-excursion (dolist (x (mines-get-neighbours idx)) (mines-goto x) (unless (get-text-property (point) 'done) (push x mines-undone-neighbours)))) (format " %s " null-str))) ((eq flag-or-unflag 'unflag) (format " %c " mines-uncover-cell-char)) ((and (memq 'flag props) (eq flag-or-unflag 'flag)) (setq face 'warning) (format " %c " mines-flagged-cell-char)) ((integerp elt) (format " %d " elt)) (t (format " %c " mines-empty-cell-mine)))) (pos (point)) (inhibit-read-only t)) (if face (insert (propertize str 'font-lock-face face)) (insert str)) (when (= (cadr (mines-index-2-matrix idx)) (1- mines-number-cols)) (backward-delete-char 1) (insert "\n")) (add-text-properties pos (point) props) (goto-char (1+ (point))))) (defun mines-show () "Display the board for a new game." (with-current-buffer (or (and (buffer-live-p mines-buffer) mines-buffer) (setq mines-buffer (get-buffer-create "*Mines*"))) (read-only-mode 1) (setq mines-game-over nil) (let ((inhibit-read-only t)) (erase-buffer) (unless (derived-mode-p 'mines-mode) (mines-mode)) (dotimes (i mines-number-rows) (dotimes (j mines-number-cols) (let* ((idx (+ (* i mines-number-cols) j)) (elt (aref mines-state idx)) (pos (point))) (mines--insert elt idx) (put-text-property pos (point) 'idx idx) (when (= j (1- mines-number-cols)) (delete-char -1) (insert "\n")) (put-text-property (1- (point)) (point) 'idx idx)))))) (display-buffer mines-buffer '(display-buffer-same-window)) (set-window-point (get-buffer-window mines-buffer) mines-start-pos)) (defun mines-current-pos () "Return the index of the cell at point." (get-text-property (point) 'idx)) (defun mines--show-all () "Show all mines after game over." (dolist (to mines-mine-positions) (save-excursion (mines-goto to) ;; Drop all flags before show the mines; that drop the flag faces. (when (eq (following-char) mines-flagged-cell-char) (mines--update-cell to mines-uncover-cell-char 'unflag)) (mines-dig 'show-mines)))) (defun mines-game-over () "Offer play a new game after uncover a bomb." (let ((inhibit-read-only t)) (put-text-property (point) (1+ (point)) 'face 'error) (mines--show-all) (if (yes-or-no-p "Game over! Play again? ") (mines) (setq mines-game-over t)))) (defun mines-game-completed () (setq mines-end-time (current-time)) (let* ((score (time-to-seconds (time-subtract mines-end-time mines-init-time))) (elapsed-time (format-seconds "%Y, %D, %H, %M, %z%S" score))) ;; save score (gamegrid-add-score (format "mines-rows-%d-cols-%d-mines-%d-scores" mines-number-rows mines-number-cols mines-number-mines) score) (message (format "Well done %s, you have completed it in %s!" user-login-name elapsed-time)))) (defun mines-flag-cell () "Flag current cell as having a mine. If called again then unflag it." (interactive) (let* ((idx (mines-current-pos)) (done (get-text-property (point) 'done)) (flagged (get-text-property (point) 'flag))) (unless idx (user-error "Wrong position!")) (unless done (cond (flagged (mines--update-cell idx mines-uncover-cell-char 'unflag)) (t (mines--update-cell idx mines-flagged-cell-char 'flag)))))) (defun mines--update-cell (idx elt &optional flag-or-unflag) (if (zerop idx) (goto-char 1) (goto-char (previous-single-property-change (point) 'idx))) (let ((to (or (next-single-property-change (point) 'idx) (point-max))) (prop (append (text-properties-at (point)) (if flag-or-unflag `(flag ,(eq flag-or-unflag 'flag)) '(done t)))) (inhibit-read-only t)) (when (eq flag-or-unflag 'unflag) (setq prop `(idx ,idx))) ;; If unflagging, then remove additional text properties. (when (eq flag-or-unflag 'unflag) (remove-text-properties (point) to '(font-lock-face flag))) (delete-region (point) to) (mines--insert elt idx prop (string mines-empty-cell-char) flag-or-unflag) (unless flag-or-unflag (aset mines-state idx '@)) (mines-goto idx))) (defun mines-dig (&optional show-mines) "Reveal the content of the cell at point." (interactive) (when (mines-mines-mode-p) (if mines-game-over (user-error "Current game is over. Try `%s' to start a new one." (substitute-command-keys "\\[mines\]")) (skip-chars-forward "[:blank:]") ; Set point in the center of the cell. (cl-labels ((uncover-fn () (let ((idx (mines-current-pos)) (inhibit-read-only t) (done (get-text-property (point) 'done))) (cond ((null idx) (user-error "Wrong position!")) (done nil) ; Already updated. (t (let ((elt (aref mines-grid idx))) ;; Don't end the game in the first trial when ;; `mines-protect-first-move' is non-nil. (when (and (eq elt t) mines-protect-first-move (mines-first-move-p)) (let ((ok-pos (cl-position-if-not (lambda (x) (eq t x)) mines-grid))) (message "Avoided game over in the first move") ;; Update mine positions. (setf (nth (cl-position idx mines-mine-positions) mines-mine-positions) ok-pos) ;; We must update `mines-grid' further: the neighbour cells ;; to IDX must show now a lower number of near bombs; the ;; cells near the new position of the bomb must increase their ;; numbers. (setq mines-grid (make-vector mines-number-cells nil)) ;; Add the mine positions. (dolist (pos mines-mine-positions) (aset mines-grid pos t)) ;; Update the numbers on neighbour cells. (mines-set-numbers) ;; Update current element. (setq elt (aref mines-grid idx)))) ;; If the cell is flagged ask for confirmation. (if (and (not show-mines) (eq (following-char) mines-flagged-cell-char)) (if (yes-or-no-p "This cell is flagged as having a bomb. Uncover it? ") (progn ; Unflag first. (mines--update-cell idx mines-uncover-cell-char 'unflag) (mines--update-cell idx elt)) (message "OK, canceled")) (mines--update-cell idx elt)) ;; Check for end of game. (cond ((and (not show-mines) (eq elt t)) ;; We lost the game; show all the mines. (mines-game-over)) (t (when (and (not show-mines) (mines-end-p)) (mines-game-completed)))))))))) (uncover-fn) (when mines-undone-neighbours (while mines-undone-neighbours (let ((to (pop mines-undone-neighbours))) (save-excursion (mines-goto to) (uncover-fn))))))))) ;;;###autoload (defun mines (&optional arg) "Play the minesweeper game. Called with a prefix prompt for the difficulty level. The target of the game is discover which cells contain mines. You reveal the content of the mine at point with \\[mines-dig\]. 1. If you look at one cell containing a mine you lost. 2. A cell without a mine having N neighbour cells containing mines shows N when you look at it. 3. A cell without a mine and without neighbour cells having mines shows the character `@' when you look at it; all adjacent cells are recursively revealed. For instance, following is a possible configuration: @ @ @ @ @ 1 2 2 1 @ 1 x x 1 @ 1 2 2 1 @ @ @ @ @ @ You can move between cells using the arrow keys, or using vi or emacs keystrokes (↑↓→←) = (kjlh) = (pnfb). You can flag a cell as having a mine with \\[mines-flag-cell\]; if you call this command again, the cell is unflagged." (interactive (let* ((prefix current-prefix-arg) (choice (and prefix (read-multiple-choice "Choose difficulty level: " '((?e "Easy" "8 columns x 8 rows and 10 mines") (?m "Medium" "16 columns x 16 rows and 40 mines") (?h "Hard" "30 columns x 16 rows and 99 mines") (?c "Custom" "C columns x R rows and M mines")))))) (when choice (mines-init (eq ?e (car choice)) (eq ?m (car choice)) (eq ?h (car choice)) (eq ?c (car choice)) (setq mines-number-cols (read-number "Number of columns: ") mines-number-rows (read-number "Number of rows: ") mines-number-mines (read-number "Number of mines: ")))) (list prefix))) (unless arg (mines-init (eq mines-difficulty-level 'easy) (eq mines-difficulty-level 'medium) (eq mines-difficulty-level 'hard) t)) (setq mines-init-time (current-time)) (mines-start) (mines-set-numbers) (mines-show)) (define-derived-mode mines-mode special-mode "mines" (let ((map mines-mode-map)) (define-key map [right] 'mines-go-right) (define-key map "f" 'mines-go-right) (define-key map "l" 'mines-go-right) (define-key map [left] 'mines-go-left) (define-key map "b" 'mines-go-left) (define-key map "h" 'mines-go-left) (define-key map "p" 'mines-go-up) (define-key map "k" 'mines-go-up) (define-key map [up] 'mines-go-up) (define-key map [down] 'mines-go-down) (define-key map "n" 'mines-go-down) (define-key map "j" 'mines-go-down) (define-key map "x" 'mines-dig) (define-key map "c" 'mines-dig) ;; (define-key map "a" 'mines-flag-cell) (define-key map "1" 'mines-flag-cell) (define-key map "m" 'mines-flag-cell) (define-key map "r" 'mines)) "Major mode for playing Minesweeper.") ;;; Predicates (defun mines-mines-mode-p () "Return non-nil if the current buffer is in `mines-mode'." (derived-mode-p 'mines-mode)) (defun mines-end-p () "Return non-nil when the game is completed." (equal mines-mine-positions (mines--find-pos nil mines-state))) (defun mines-first-move-p () "Return non-nil if any cell has been revealed yet." (cl-every 'null mines-state)) (provide 'mines) ;;; mines.el ends here --8<-----------------------------cut here---------------end--------------->8--- --8323329-1831888515-1509970217=:17869--