[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/hiddenquote 9dac184 01/14: Initial commit
From: |
Stefan Monnier |
Subject: |
[elpa] externals/hiddenquote 9dac184 01/14: Initial commit |
Date: |
Tue, 9 Feb 2021 18:06:52 -0500 (EST) |
branch: externals/hiddenquote
commit 9dac184568214de2300e7b685d43e220262feaa2
Author: Mauro Aranda <maurooaranda@gmail.com>
Commit: Mauro Aranda <maurooaranda@gmail.com>
Initial commit
---
.gitignore | 36 ++
hiddenquote.el | 1519 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
puzzles/1.ipuz | 947 +++++++++++++++++++++++++++++++++++
puzzles/2.ipuz | 532 ++++++++++++++++++++
puzzles/3.ipuz | 798 +++++++++++++++++++++++++++++
puzzles/4.ipuz | 532 ++++++++++++++++++++
6 files changed, 4364 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..24876cb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,36 @@
+# Files for Git to ignore.
+
+# Copyright 2021 Mauro Aranda
+
+# This file is part of claringrilla.
+
+# claringrilla 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.
+
+# claringrilla 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 claringrilla. If not, see <https://www.gnu.org/licenses/>.
+
+# Logs and temp.
+*.log
+*.tmp
+
+# Patches
+*.patch
+
+# Junk
+*~
+.#*
+\#*\#
+
+# Compiled ELisp
+*.elc
+
+# Design
+hiddenquote.org
diff --git a/hiddenquote.el b/hiddenquote.el
new file mode 100644
index 0000000..c27a191
--- /dev/null
+++ b/hiddenquote.el
@@ -0,0 +1,1519 @@
+;;; hiddenquote.el --- Major mode for doing hidden quote puzzles -*-
lexical-binding: t -*-
+
+;; Copyright (C) 2020-2021 by Mauro Aranda
+
+;; Author: Mauro Aranda <maurooaranda@gmail.com>
+;; Maintainer: Mauro Aranda <maurooaranda@gmail.com>
+;; Created: Sun Dec 13 11:10:00 2020
+;; Version: 0.1
+;; Package-Version: 0.1
+;; Package-Requires: ((emacs "25.1"))
+;; URL: http://www.mauroaranda.com
+;; Keywords: games
+
+;; This file is NOT part of GNU Emacs.
+
+;; hiddenquote 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.
+;;
+;; hiddenquote 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 hiddenquote. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This file contains all the hidden quote puzzle rendering, major mode
+;; and commands. It also defines a source to retrieve puzzles of
+;; the hidden quote type, created by myself.
+;;
+;; Usage:
+;; (require 'hiddenquote)
+;; M-x hiddenquote
+;; Select the source you want to retrieve the puzzle from.
+;; With a prefix argument, the command will prompt for a specific ID number.
+;;
+;; Read the definitions for each word and fill the character cells
+;; to complete the puzzle, marking the syllables as used, as you use them,
+;; Move around the buffer with the usual movement commands.
+;; Edit each character cell with the usual editing commands.
+;; To mark a syllable as used, click it with the mouse or move point to
+;; it and hit RET.
+;; When the puzzle is complete, you should be able to read the hidden
+;; quote in the highlighted cells.
+;;
+;; To change the puzzle sources, customize `hiddenquote-sources'.
+;;
+;; M-x customize-group RET hiddenquote
+;; to see other customization options.
+;; To see what faces you can customize, type
+;; M-x customize-group RET hiddenquote-faces
+
+;;; Code:
+
+;; Requirements.
+(require 'eieio)
+(require 'eieio-base)
+(require 'cl-lib)
+(require 'wid-edit)
+(require 'subr-x)
+(require 'json)
+
+(defvar json-pretty-print-max-secs) ; json.el
+
+;; Customization.
+(defgroup hiddenquote nil
+ "Solve hiddenquote in Emacs."
+ :group 'games
+ :version "0.1")
+
+(defcustom hiddenquote-sources
+ '(("puzzles" ipuz hiddenquote-get-local-puzzle)
+ ("hidden-quote" ipuz hiddenquote-get-hidden-quote-puzzle))
+ "An alist of hidden quote puzzle sources.
+Each member is of the form (SOURCE-NAME ORIG-FORMAT FUNCTION).
+
+SOURCE-NAME should be a string, an unique name for the source/publisher.
+ORIG-FORMAT should be a symbol naming the format in which the puzzle
+is retrieved. At the moment, there is builtin support for two formats:
+.ipuz format and html format.
+
+REST should be a function that retrieves an instance of
+`hiddenquote-hidden-quote-puzzle'.
+
+The function handles the prefix arg given to `hiddenquote'."
+ :type '(repeat :tag "Hidden Quote Sources"
+ (choice
+ (list :tag "IPUZ"
+ (string :tag "Source name")
+ (const :tag "Format: ipuz" ipuz)
+ (function :tag "Custom function"))
+ (list :tag "HTML"
+ (string :tag "Source name")
+ (const :tag "Format: html" html)
+ (function :tag "Function"))
+ (sexp :tag "Custom source")))
+ :package-version '(hiddenquote . "0.1"))
+
+(defcustom hiddenquote-mode-hook nil
+ "Hook to run when entering `hiddenquote-mode'."
+ :type 'hook
+ :package-version '(hiddenquote . "0.1"))
+
+(defcustom hiddenquote-buffer-name-format "*Hidden Puzzle %n*"
+ "Format to use for the buffer name.
+
+Supported escapes:
+%t: Insert the puzzle title.
+%n: Insert the puzzle number."
+ :type 'string
+ :package-version '(hiddenquote . "0.1"))
+
+(defcustom hiddenquote-upcase-chars (display-graphic-p)
+ "Non-nil if you don't want chars to get upcased automatically.
+
+In TTYs, it is recommended to set this option to nil,
+so characters like \"á\" can be displayed without problem. This is relevant
+only if the puzzles you play contain such characters, of course."
+ :type 'boolean
+ :package-version '(hiddenquote . "0.1"))
+
+(defcustom hiddenquote-automatic-check nil
+ "Non-nil means check after each change if the answer is right or wrong."
+ :type 'boolean
+ :package-version '(hiddenquote . "0.1"))
+
+(defcustom hiddenquote-directory
+ (file-name-as-directory (locate-user-emacs-file "hiddenquote"))
+ "Directory where to store the puzzles' files."
+ :type 'directory
+ :package-version '(hiddenquote . "0.1"))
+
+(defcustom hiddenquote-show-time t
+ "Whether to show the time passed since starting playing a puzzle."
+ :type 'boolean
+ :package-version '(hiddenquote . "0.1"))
+
+(defcustom hiddenquote-skip-used-syllables nil
+ "If non-nil, tabbing skips used syllables in the Syllables buffer."
+ :type 'boolean
+ :package-version '(hiddenquote . "0.1"))
+
+(defgroup hiddenquote-faces nil
+ "Faces used by `hiddenquote'."
+ :group 'hiddenquote)
+
+(defface hiddenquote
+ '((t (:inherit default :height 1.5)))
+ "Base face used by `hiddenquote'."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-title
+ '((((class color) (background light))
+ (:foreground "#444" :height 0.75 :inherit hiddenquote))
+ (((class color) (background dark))
+ (:foreground "white" :height 0.75 :inherit hiddenquote))
+ (t (:inherit hiddenquote)))
+ "Face used for the title."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-sep
+ '((t (:height 0.1 :inherit default)))
+ "Face to use in character-separating spaces."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-word-number
+ '((((type tty))
+ (:foreground "black" :background "white" :inherit hiddenquote))
+ (((class color))
+ (:foreground "white" :background "#999" :inherit hiddenquote))
+ (t (:inherit hiddenquote)))
+ "Face used for word indexes."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-word-right
+ '((t (:foreground "white" :background "#5adc5f" :inherit hiddenquote)))
+ "Face used for correct answers."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-word-wrong
+ '((t (:foreground "white" :background "#d80026" :inherit hiddenquote)))
+ "Face used for wrong answers."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-value
+ '((t (:foreground "#333" :background "gray80" :inherit hiddenquote)))
+ "Face for characters inserted by the user."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-quote-value
+ '((t (:foreground "#333" :background "#fcee21" :inherit hiddenquote)))
+ "Face for the characters in the grid that hold the quote."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-used-syllable
+ '((t (:foreground "black" :background "yellow" :inherit hiddenquote)))
+ "Face for syllables that the user has used."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-unused-syllable
+ '((t (:inherit hiddenquote)))
+ "Face for syllables that the user hasn't used."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-highlight-syllable
+ '((t (:background "#fcee21" :height 1.5 :inherit default)))
+ "Face for highlighting syllables when the mouse hovers."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-widget-highlight
+ '((t (:background "#fcee21" :inherit hiddenquote)))
+ "Face for highlighting a widget."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-highlight
+ '((((class color) (background light))
+ (:background "#fcee21" :inherit 'default))
+ (((class color) (background dark))
+ (:background "#fcee21" :foreground "black"))
+ (t (:inherit default)))
+ "Face for highlighting the current definition."
+ :package-version '(hiddenquote . "0.1"))
+
+(defface hiddenquote-doc
+ '((t (:foreground "#999" :inherit default)))
+ "Face used for the puzzle commentary."
+ :package-version '(hiddenquote . "0.1"))
+
+;; Variables.
+(defvar hiddenquote-mode-map
+ (let ((map (make-sparse-keymap)))
+ ;; Mouse support.
+ (define-key map [down-mouse-2] #'widget-button-click)
+ (define-key map [down-mouse-1] #'widget-button-click)
+ ;; Widget movement.
+ (define-key map "\t" #'widget-forward)
+ (define-key map [right] #'hiddenquote-forward)
+ (define-key map [left] #'hiddenquote-backward)
+ (define-key map [(shift tab)] #'hiddenquote-backward)
+ (define-key map [backtab] #'hiddenquote-backward)
+ (define-key map "\C-n" #'hiddenquote-next)
+ (define-key map [down] #'hiddenquote-next)
+ (define-key map "\C-p" #'hiddenquote-prev)
+ (define-key map [up] #'hiddenquote-prev)
+ (define-key map [(meta ?g) (meta ?g)] #'hiddenquote-goto-word)
+ ;; Done.
+ (define-key map [(control ?x) (control ?s)] #'hiddenquote-save)
+ (define-key map [(control ?x) ?!] #'hiddenquote-give-up)
+ (define-key map [(control ?x) ?k] #'hiddenquote-quit)
+ map)
+ "Keymap for the buffer where the grid is at.")
+
+(defvar hiddenquote-character-map
+ (let ((map (copy-keymap hiddenquote-mode-map)))
+ ;; Movement.
+ (define-key map "\C-f" #'widget-forward)
+ (define-key map [right] #'widget-forward)
+ (define-key map "\C-b" #'widget-backward)
+ (define-key map [left] #'widget-backward)
+ (define-key map "\C-a" #'hiddenquote-move-beginning-of-word)
+ (define-key map [home] #'hiddenquote-move-beginning-of-word)
+ (define-key map "\C-e" #'hiddenquote-move-end-of-word)
+ (define-key map [end] #'hiddenquote-move-end-of-word)
+ (define-key map "\C-j" #'hiddenquote-next)
+ ;; Editing.
+ (define-key map "\C-k" #'hiddenquote-kill-word)
+ (define-key map [(control shift backspace)] #'hiddenquote-kill-whole-word)
+ (define-key map [backspace] #'hiddenquote-delete-backward-char)
+ (define-key map [delete] #'delete-char)
+ (define-key map [?\d] #'hiddenquote-delete-backward-char)
+ ;; Actions.
+ (define-key map "?" #'hiddenquote-check-answer)
+ (define-key map "\C-m" #'widget-field-activate)
+ ;; Avoid trouble.
+ (define-key map " " #'ignore)
+ (define-key map "\C-o" #'ignore)
+ map)
+ "Keymap for the `hiddenquote-character' widget.")
+
+(defvar hiddenquote-syllables-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map widget-keymap)
+ (define-key map "\t" #'hiddenquote-forward-syllable)
+ (define-key map [(shift tab)] #'hiddenquote-backward-syllable)
+ (define-key map [backtab] #'hiddenquote-backward-syllable)
+ map))
+
+(defvar hiddenquote-previous-window-configuration nil
+ "Holds the window configuration before `hiddenquote' creates its windows.")
+
+(defvar-local hiddenquote-current nil
+ "Hold the current puzzle, a `hiddenquote-grid' widget.")
+
+(defvar-local hiddenquote-definition-markers nil
+ "A vector of cons-cells (START . END), the START and END of a definition.
+
+Used locally in the definitions buffer, to highlight/unhighlight them.")
+
+(defvar-local hiddenquote-syllables nil
+ "Hold all syllable widgets in the syllables buffer.")
+
+(defvar-local hiddenquote-buffer nil "The buffer where the grid is at.")
+
+;; Utilities.
+(defun hiddenquote-insert (c face)
+ "Insert C (a character or a string), with face FACE, using `widget-insert'."
+ (let ((s (if (characterp c) (char-to-string c) c)))
+ (widget-insert (propertize s 'face face))))
+
+(defun hiddenquote-clear-buffer ()
+ "Clear the current buffer."
+ (let ((inhibit-read-only t))
+ (erase-buffer)))
+
+(defun hiddenquote-refuse-kill-buffer ()
+ "Refuse to kill a definitions or a syllables buffer."
+ (error "Can't kill %s buffer. Kill the main puzzle buffer instead"
+ (buffer-name)))
+
+(defun hiddenquote-ensure-file-exists (file)
+ "Create the file named FILE if it not exists."
+ (unless (file-exists-p file)
+ (let ((paren-dir (file-name-directory file)))
+ (when (and paren-dir (not (file-exists-p paren-dir)))
+ (make-directory paren-dir t)))
+ (write-region "" nil file nil 0)))
+
+(defun hiddenquote-expand-puzzle-file-name (filename source)
+ "Use `expand-file-name' on FILENAME, using SOURCE as a relative directory."
+ (expand-file-name filename
+ (expand-file-name
+ (file-name-as-directory source)
+ (file-name-as-directory hiddenquote-directory))))
+
+(defun hiddenquote-widget-backward ()
+ "Compatibility function, to avoid `widget-backward' to skip widgets."
+ (unless (eobp)
+ (forward-char 1)))
+
+;; Classes.
+;; A `hiddenquote-hidden-quote-puzzle' represents a hidden quote puzzle,
+;; following closely its ipuz spec.
+(defclass hiddenquote-hidden-quote-puzzle ()
+ (;; Mandatory.
+ (version :initarg :version
+ :initform "http://ipuz.org/v2"
+ :type string
+ :documentation "The ipuz version")
+ (kind :initarg :kind
+ :initform ["http://mauroaranda.com/puzzles/hiddenquote#1"
+ "http://ipuz.org/crossword#1"]
+ :type vector
+ :documentation "The ipuz PuzzleKind field")
+ ;; Optional.
+ (id :initarg :id
+ :initform 1
+ :type natnum
+ :custom integer
+ :documentation "An unique id for the ipuz file, a positive number.")
+ (author :initarg :author
+ :initform ""
+ :type string
+ :custom string
+ :documentation "The ipuz file author.")
+ (editor :initarg :editor
+ :initform ""
+ :type string
+ :custom string
+ :documentation "The ipuz file editor.")
+ (copyright :initarg :copyright
+ :initform ""
+ :type string
+ :documentation "Copyright for the ipuz file.")
+ (publisher :initarg :publisher
+ :initform ""
+ :type string
+ :custom string
+ :documentation "Publisher of the puzzle.")
+ (title :initarg :title
+ :initform ""
+ :type string
+ :documentation "Title of the puzzle.")
+ (url :initarg :url
+ :initform ""
+ :type string
+ :documentation "Permanent url for the puzzle.")
+ (origin :initarg :origin
+ :initform ""
+ :type string
+ :custom string
+ :documentation "Program that created the puzzle.")
+ (created-date :initarg :created-date
+ :initform (format-time-string "%m/%d/%Y" (current-time))
+ :type string
+ :custom string
+ :documentation "Date of creation, in MM/DD/YYYY format.")
+ (description :initarg :description
+ :initform ""
+ :type string
+ :custom text
+ :documentation
+ "Insert documentation for the user, like whose quote it is.")
+ ;; Crossword fields.
+ (clues :initarg :clues
+ :initform []
+ :type vector
+ :custom (vector
+ (repeat :inline t
+ (list (string :tag "Answer") (string :tag "Clue"))))
+ :documentation
+ "Clues and answers used in the puzzle. Have to be ordered.")
+ (saved :initarg :saved
+ :initform nil
+ :type (or null hiddenquote-edit)
+ :documentation "Saved state for the puzzle.")
+ ;; Hiddenquote extensions.
+ (field-lengths :initarg :field-lengths
+ :initform []
+ :documentation "Hold the length of each answer."
+ :type vector)
+ (arrows :initarg :arrows
+ :initform ""
+ :type string
+ :custom string
+ :documentation "Positions of highlighted characters.
+It has to be of the form ARR1,ARR2")
+ (syllables :initarg :syllables
+ :initform []
+ :type vector
+ :custom (vector (repeat :inline t
+ (string :tag "Syllable")))
+ :documentation "Syllables (not necessarily sorted.)")
+ (qquote :initarg :quote
+ :initform ""
+ :type string
+ :custom string
+ :documentation "The quote that is hidden in the puzzle.")
+ (subject :initarg :subject
+ :initform ""
+ :type string
+ :custom string
+ :documentation
+ "The subject for the puzzle (e.g., general, chemistry, etc.")
+ ;; Not part of ipuz.
+ (file :initarg :file
+ :initform ""
+ :type string
+ :documentation "Filename to save this puzzle, in ipuz format.")))
+
+;; A `hiddenquote-edit' object is an object that holds the user edits
+;; in a playing session. This information can be saved, in an .ipuz file,
+;; effectively saving user progress.
+(defclass hiddenquote-edit ()
+ ((answers :initarg :answers
+ :initform nil
+ :documentation "User answers."
+ :type (or vector null))
+ (checked-answers :initarg :checked-answers
+ :initform nil
+ :documentation "Answers that the user has checked."
+ :type list)
+ (used-syllables :initarg :used-syllables
+ :initform nil
+ :documentation "A list with the indexes of used-syllables."
+ :type list)
+ (elapsed-time :initarg :elapsed-time
+ :initform 0
+ :documentation "Time elapsed while completing the puzzle."
+ :type (or number list))))
+
+(cl-defmethod hiddenquote-create-grid ((self hiddenquote-hidden-quote-puzzle))
+ "Create a grid to complete SELF.
+
+Returns the `hiddenquote-grid' widget created."
+ (let* ((w (widget-create 'hiddenquote-grid self)) ; Create the grid.
+ (def-buff (get-buffer-create (concat (buffer-name) " - Definitions")))
+ (cbuff (current-buffer))
+ (inhibit-read-only t)
+ window)
+ ;; Fill the doc, so it looks better.
+ (fill-region (overlay-start (widget-get w :doc-overlay))
+ (overlay-end (widget-get w :doc-overlay)))
+ ;; Put point at the first word.
+ (goto-char (point-min))
+ ;; Create definitions.
+ (with-current-buffer def-buff
+ (erase-buffer)
+ (add-hook 'kill-buffer-hook #'hiddenquote-refuse-kill-buffer nil t)
+ (hiddenquote-insert "Definitions\n\n" 'hiddenquote-title)
+ (let* ((defs (mapcar (lambda (el)
+ (cadr el))
+ (oref self clues)))
+ (i 0)
+ markers opoint)
+ (dolist (def defs)
+ (setq opoint (point))
+ (insert (format "%d. %s." (setq i (1+ i)) def))
+ (fill-region opoint (point))
+ (push (cons (copy-marker opoint) (point-marker)) markers)
+ (insert "\n\n"))
+ (setq hiddenquote-definition-markers (vconcat (reverse markers))))
+ (goto-char (point-min))
+ (setq buffer-read-only t)
+ (setq hiddenquote-buffer cbuff))
+ (setq window (split-window nil nil 'right))
+ (set-window-buffer window def-buff)
+ (set-window-dedicated-p window t)
+ (set-window-parameter window 'no-other-window t)
+ ;; Syllables.
+ (let ((syll-buff (get-buffer-create (concat (buffer-name) " - Syllables")))
+ (syllables (oref self syllables))
+ (used (and (oref self saved)
+ (oref (oref self saved) used-syllables))))
+ (with-current-buffer syll-buff
+ (kill-all-local-variables)
+ (hiddenquote-clear-buffer)
+ (remove-overlays)
+ (let ((i 0)
+ (max (apply #'max (mapcar (lambda (el)
+ (length el))
+ syllables))))
+ (setq hiddenquote-syllables
+ (cl-mapcar
+ (lambda (syl)
+ ;; FIXME: Maybe it looks better center-aligned.
+ (let ((w (widget-create
+ 'hiddenquote-syllable
+ :tag (concat syl (make-string
+ (1+ (- max (length syl)))
+ ?\s))
+ :pressed-face 'hiddenquote-highlight-syllable
+ :value (member i used))))
+ (when (= 0 (% (setq i (1+ i)) 7))
+ (insert "\n"))
+ w))
+ syllables)))
+ ;; Avoid highlighting the last syllable too much.
+ (hiddenquote-insert " " 'hiddenquote-sep)
+ (setq-local widget-button-click-moves-point t)
+ (use-local-map hiddenquote-syllables-map)
+ (widget-setup)
+ (setq buffer-read-only t)
+ (setq hiddenquote-buffer cbuff)
+ (add-hook 'kill-buffer-hook #'hiddenquote-refuse-kill-buffer nil t)
+ (when (< emacs-major-version 28)
+ (add-hook 'widget-backward-hook
+ #'hiddenquote-widget-backward nil t))
+ (goto-char (point-min)))
+ (set-window-buffer (split-window window) syll-buff))
+ ;; In case we loaded an already completed puzzle.
+ (unless (hiddenquote-puzzle-complete-p)
+ (hiddenquote-timer-start-timer (widget-get w :hiddenquote-timer)))
+ w))
+
+(cl-defmethod hiddenquote-puzzle-to-ipuz
+ ((self hiddenquote-hidden-quote-puzzle))
+ "Return a string that is an ipuz representation of SELF."
+ (let ((width (1+ (apply #'max (cl-mapcar (lambda (clue)
+ (length (nth 0 clue)))
+ (oref self clues)))))
+ (height (length (oref self clues)))
+ (arrows (let ((lst (split-string (oref self arrows) ",")))
+ (cons (string-to-number (car lst))
+ (string-to-number (cadr lst)))))
+ (edits (oref self saved)))
+ (with-temp-buffer
+ (insert
+ (json-encode `(("version" . ,(oref self version))
+ ("kind" . ,(oref self kind))
+ ("copyright" . ,(oref self copyright))
+ ("publisher" . ,(oref self publisher))
+ ("url" . ,(oref self url))
+ ("uniqueid" . ,(number-to-string (oref self id)))
+ ("title" . ,(oref self title))
+ ("author" . ,(oref self author))
+ ("editor" . ,(oref self editor))
+ ("date" . ,(oref self created-date))
+ ("origin" . ,(oref self origin))
+ ("intro" . ,(oref self description))
+ ("dimensions" . ,(list (cons "width" width)
+ (cons "height" height)))
+ ("puzzle" .
+ ,(let ((i 1))
+ (vconcat
+ (cl-mapcar
+ (lambda (clue)
+ (prog1
+ (let ((len (length (nth 0 clue))))
+ (vconcat (list (number-to-string i))
+ (make-list len 0)
+ (when (> (- width len) 0)
+ (make-list (- width len) nil))))
+ (setq i (1+ i))))
+ (oref self clues)))))
+ ("clues" .
+ ,(list
+ (cons "Clues"
+ (let ((i 1))
+ (vconcat
+ (cl-mapcar
+ (lambda (clue)
+ (prog1
+ `(("number" . ,i)
+ ("clue" . ,(nth 1 clue))
+ ("answer" . ,(nth 0 clue))
+ ("enumeration" . ,(length
+ (nth 0 clue))))
+ (setq i (1+ i))))
+ (oref self clues)))))))
+ ("answer" . ,(oref self qquote))
+ ("zones" .
+ ,(let ((arr1 (car arrows))
+ (arr2 (cdr arrows))
+ (fake-height (1- height)))
+ (vconcat
+ (list
+ (list
+ (cons "rect" (vector 0 0 0 fake-height))
+ (cons "style" '(("border" . 0))))
+ (list
+ (cons "rect" (vector arr1 0 arr1 fake-height))
+ (cons "style" '(("highlight" . t))))
+ (list
+ (cons "rect" (vector arr2 0 arr2 fake-height))
+ (cons "style" '(("highlight" . t))))))))
+ ("saved" .
+ ,(list (cons "answers" (if edits (oref edits answers)
+ []))
+ (cons "checked-answers"
+ (when edits
+ (oref edits checked-answers)))
+ (cons "used-syllables"
+ (when edits
+ (oref edits used-syllables)))
+ (cons "elapsed-time"
+ (if edits
+ (oref edits elapsed-time)
+ 0))))
+ ("volatile" . (("com.hiddenquote:arrows" . "")
+ ("com.hiddenquote:subject" . "")
+ ("com.hiddenquote:syllables" . "")))
+ ("com.hiddenquote:arrows" . ,(oref self arrows))
+ ("com.hiddenquote:subject" . ,(oref self subject))
+ ("com.hiddenquote:syllables" .
+ ,(vconcat (sort (copy-sequence
+ (oref self syllables))
+ #'string-lessp))))))
+ (let ((json-pretty-print-max-secs 0.0))
+ (json-pretty-print (point-min) (point-max)))
+ (buffer-string))))
+
+(cl-defmethod hiddenquote-load-saved-puzzle
+ ((self hiddenquote-hidden-quote-puzzle))
+ "Return a `hiddenquote-edit' instance, read from the file slot in SELF.
+
+If the file does not exist, the `hiddenquote-edit' instance has default
+values."
+ (if (file-exists-p (oref self file))
+ (let ((temp (hiddenquote-puzzle-from-ipuz
+ (with-current-buffer (find-file-noselect (oref self file) t)
+ (prog1 (buffer-string)
+ (kill-buffer))))))
+ (oref temp saved))
+ (make-instance 'hiddenquote-edit)))
+
+(cl-defmethod hiddenquote-format-buffer-name
+ ((self hiddenquote-hidden-quote-puzzle))
+ "Return a buffer name, using `hiddenquote-buffer-name-format' and SELF."
+ (let ((fn (if (fboundp 'string-replace)
+ #'string-replace
+ #'replace-regexp-in-string))
+ ret)
+ (setq ret (funcall fn "%t" (oref self title)
+ hiddenquote-buffer-name-format))
+ (setq ret (funcall fn "%n" (if (stringp (oref self id))
+ (oref self id)
+ (number-to-string (oref self id)))
+ ret))))
+
+;; Hiddenquote Widget.
+;; Use a widget, `hiddenquote-grid', to hold everything. Its tag is
+;; the puzzle's title. The value holds all the answers in the grid,
+;; and the documentation is the puzzle intro.
+(define-widget 'hiddenquote-grid 'default
+ "A widget for a hidden quote puzzle."
+ :format "%t\n%d\n%v\n%T"
+ :format-handler #'hiddenquote-grid-format-handler
+ :convert-widget #'hiddenquote-grid-convert-widget
+ :value-create #'hiddenquote-grid-value-create)
+
+(define-widget 'hiddenquote-word 'default
+ "A widget for each answer in a `hiddenquote-grid' widget."
+ :format "%v\n"
+ :value-create #'hiddenquote-word-value-create
+ :value-get #'hiddenquote-word-value-get
+ :value-set #'hiddenquote-word-value-set
+ :validate #'hiddenquote-word-validate
+ :notify #'hiddenquote-word-notify)
+
+(define-widget 'hiddenquote-number 'item
+ "A clickable item. When actioned, shows if the answer is right or wrong."
+ :format "%[%t%]"
+ :value 'unchecked
+ :button-face-get #'hiddenquote-number-button-face-get
+ :mouse-face 'hiddenquote-widget-highlight
+ :pressed-face 'hiddenquote-widget-highlight
+ :action #'hiddenquote-number-action)
+
+(define-widget 'hiddenquote-character 'character
+ "A character widget for the `hiddenquote-word' widget."
+ :format "%v"
+ :value ?\s
+ :notify #'hiddenquote-character-notify
+ :value-to-internal (lambda (_widget value)
+ (if (stringp value) value (char-to-string value)))
+ :keymap hiddenquote-character-map
+ :help-echo "")
+
+(define-widget 'hiddenquote-timer 'item
+ "A widget that shows the elapsed time starting from its creation."
+ :format "%v\n"
+ :value-create #'hiddenquote-timer-value-create
+ :hiddenquote-start-value 0
+ :sample-face 'hiddenquote-title)
+
+;; Use clickable-item widgets for the syllables, so the user can toggle their
+;; state. When the user marks a syllable as used, draw it with the
+;; `hiddenquote-used-syllable' face, otherwise if the user marks a syllable as
+;; unused, draw it with `hiddenquote-unused-syllable' face.
+(define-widget 'hiddenquote-syllable 'item
+ "Used/unused state for a syllable."
+ :format "%[%v%]"
+ :value-create #'hiddenquote-syllable-value-create
+ :button-face-get #'hiddenquote-syllable-button-face-get
+ :mouse-face 'hiddenquote-highlight-syllable
+ :action #'widget-toggle-action
+ :notify #'hiddenquote-syllable-notify)
+
+(defun hiddenquote-grid-convert-widget (widget)
+ "Initialize :tag and :doc properties of `hiddenquote-grid' widget WIDGET.
+Use the number slot of a `hiddenquote-hidden-quote-puzzle' object to build
+the :tag, and use the description slot to build the :doc."
+ (let ((puzzle (nth 0 (widget-get widget :args))))
+ (widget-put widget :hiddenquote puzzle)
+ (widget-put widget :args nil) ; Don't need them anymore.
+ (widget-put widget :tag (propertize
+ (oref puzzle title)
+ 'face 'hiddenquote-title))
+ (widget-put widget :doc (oref puzzle description)))
+ widget)
+
+(defun hiddenquote-grid-value-create (widget)
+ "Create a `hiddenquote-grid' widget WIDGET, by creating each word."
+ (let* ((puzzle (widget-get widget :hiddenquote))
+ (lengths (oref puzzle field-lengths))
+ children buttons)
+ (dotimes (i (length lengths))
+ ;; FIXME: Maybe it looks better center-aligned.
+ (let ((opoint (point)))
+ (push (widget-create-child-and-convert
+ widget 'hiddenquote-number
+ :hiddenquote-word-number i
+ :tag (format "%-2d" (1+ i))
+ :value (if (and (oref puzzle saved)
+ (oref (oref puzzle saved) checked-answers))
+ ;; `json-encode' outputs the symbols as strings,
+ ;; so get them back as symbols.
+ (intern-soft
+ (nth i (oref (oref puzzle saved) checked-answers)))
+ 'unchecked))
+ buttons)
+ (hiddenquote-insert " " 'hiddenquote-sep)
+ (add-text-properties opoint (point)
+ `(cursor-sensor-functions
+ ,(list
+ (lambda (_window _prev action)
+ (if (eq action 'left)
+ (hiddenquote-unhighlight-definition i)
+ (hiddenquote-highlight-definition i)
+ (widget-forward 1)))))))
+ (push
+ (widget-create-child-and-convert
+ widget 'hiddenquote-word
+ :hiddenquote-word-number i
+ :hiddenquote-word-length (aref lengths i)
+ :value (if (and (oref puzzle saved)
+ (oref (oref puzzle saved) answers))
+ (aref (oref (oref puzzle saved) answers) i)
+ (make-string (aref lengths i) ?\s)))
+ children)
+ (hiddenquote-insert "\n" 'hiddenquote-sep))
+ (widget-put widget :buttons (reverse buttons))
+ (widget-put widget :children (reverse children))))
+
+(defun hiddenquote-grid-format-handler (widget ch)
+ "Recognize escape character CH when creating a `hiddenquote-grid' WIDGET."
+ (let ((saved (oref (widget-get widget :hiddenquote) saved)))
+ (cond ((eq ch ?T) ; Print time.
+ (when hiddenquote-show-time
+ (widget-put widget :hiddenquote-timer
+ (widget-create
+ 'hiddenquote-timer
+ :hiddenquote-buffer (current-buffer)
+ :hiddenquote-start-value
+ (if (and saved (oref saved elapsed-time))
+ (if (fboundp 'time-convert)
+ (time-convert ; Emacs 27.1
+ (oref saved elapsed-time)
+ 'integer)
+ (oref saved elapsed-time))
+ 0)))))
+ (t (widget-default-format-handler widget ch)))))
+
+(defun hiddenquote-word-value-create (widget)
+ "Create a `hiddenquote-word' widget WIDGET, by creating each character cell.
+Highlight the character cells that form the quote."
+ (let ((arrows (split-string
+ (oref (widget-get (widget-get widget :parent) :hiddenquote)
+ arrows)
+ ","))
+ (opoint (point))
+ (value (widget-get widget :value))
+ children)
+ (dotimes (i (widget-get widget :hiddenquote-word-length))
+ (push
+ (widget-create-child-and-convert
+ widget 'hiddenquote-character
+ :value (aref value i)
+ :value-face (if (or (= (1+ i) (string-to-number (car arrows)))
+ (= (1+ i) (string-to-number (cadr arrows))))
+ 'hiddenquote-quote-value
+ 'hiddenquote-value))
+ children)
+ (unless (eql i (1- (widget-get widget :hiddenquote-word-length)))
+ (hiddenquote-insert " " 'hiddenquote-sep)))
+ (add-text-properties opoint (point)
+ `(cursor-sensor-functions
+ ,(list
+ (lambda (_window _prev action)
+ (if (eq action 'entered)
+ (hiddenquote-highlight-definition
+ (widget-get widget
+ :hiddenquote-word-number))
+ (hiddenquote-unhighlight-definition
+ (widget-get widget
+ :hiddenquote-word-number)))))))
+ (widget-put widget :children (reverse children))))
+
+(defun hiddenquote-word-value-get (widget)
+ "Return `hiddenquote-word' widget WIDGET's value."
+ (apply #'string (mapcar #'widget-value (widget-get widget :children))))
+
+(defun hiddenquote-word-value-set (widget value)
+ "Set the value of `hiddenquote-word' widget WIDGET to VALUE."
+ (let ((children (widget-get widget :children)))
+ (if (nthcdr (1- (length value)) children)
+ (let ((i 0))
+ (dolist (child children)
+ (widget-value-set child (aref value i))
+ (setq i (1+ i))))
+ (error "Expected string of %d characters" (length children)))))
+
+(defun hiddenquote-after-change (from to _old)
+ "Compatibility `after-change-function' for Emacs < 28.
+Starting from Emacs 28.1, `widget-after-change' passes a fake event to the
+field :notify function. We use that fake event to figure out what to do.
+
+Notify the widget between FROM and TO about a change."
+ (let ((field (widget-field-find from))
+ (other (widget-field-find to)))
+ (when field
+ (unless (eq field other)
+ (error "Change in different fields"))
+ (widget-apply field :notify field (list 'after-change from to)))))
+
+(defun hiddenquote-word-notify (widget child event)
+ "Notify the `hiddenquote-word' widget WIDGET about a change in CHILD.
+
+If the car of EVENT is `before-change', this function just calls
+`widget-default-notify'. If the of EVENT is `after-change', then advance
+point to some other widget and maybe check the answer."
+ ;; When CHILD is the last WIDGET's children, go to the first child.
+ (when (and (eq (car-safe event) 'after-change)
+ (not (eql (nth 1 event) (nth 2 event))))
+ (if (eq child (car (last (widget-get widget :children))))
+ (goto-char (overlay-start
+ (widget-get (car (widget-get widget :children))
+ :field-overlay)))
+ (widget-forward 1)))
+ (when (and (eq (car-safe event) 'after-change)
+ (or hiddenquote-automatic-check
+ (not
+ (eq (widget-value
+ (nth (widget-get widget :hiddenquote-word-number)
+ (widget-get (widget-get widget :parent) :buttons)))
+ 'unchecked))))
+ (hiddenquote-check-answer))
+ (widget-default-notify widget child event))
+
+(defun hiddenquote-word-validate (widget)
+ "Nil if current value of hiddenquote-word WIDGET is correct."
+ (let ((val (widget-value widget))
+ (puzzle (widget-get (widget-get widget :parent) :hiddenquote)))
+ (unless (string-collate-equalp
+ val
+ (car (aref (oref puzzle clues)
+ (widget-get widget :hiddenquote-word-number)))
+ nil t)
+ widget)))
+
+(defun hiddenquote-character-notify (widget child &optional event)
+ "Replace characters in character widget WIDGET, so its size is always 1.
+Checks that the car of EVENT is `after-change'.
+
+Calls `widget-default-notify' with WIDGET, CHILD and EVENT as args."
+ (let ((start (nth 1 event))
+ (end (nth 2 event))
+ (parent (widget-get widget :parent))
+ val)
+ (when (eq (car-safe event) 'after-change)
+ (if (eql start end)
+ ;; Hack! Restore the cursor-sensor-functions property here,
+ ;; because `widget-value-set' drops it.
+ (add-text-properties (1- (widget-get parent :from))
+ (widget-get parent :to)
+ `(cursor-sensor-functions
+ ,(list
+ (lambda (_window _prev action)
+ (if (eq action 'entered)
+ (hiddenquote-highlight-definition
+ (widget-get
+ parent
+ :hiddenquote-word-number))
+ (hiddenquote-unhighlight-definition
+ (widget-get
+ parent
+ :hiddenquote-word-number)))))))
+ (setq val (buffer-substring-no-properties start (1+ start)));end))
+ (widget-value-set widget (if hiddenquote-upcase-chars
+ (upcase val)
+ val))
+ ;; Hack! Restore the cursor-sensor-functions property here,
+ ;; because `widget-value-set' drops it.
+ (add-text-properties (1- (widget-get parent :from))
+ (widget-get parent :to)
+ `(cursor-sensor-functions
+ ,(list
+ (lambda (_window _prev action)
+ (if (eq action 'entered)
+ (hiddenquote-highlight-definition
+ (widget-get
+ parent
+ :hiddenquote-word-number))
+ (hiddenquote-unhighlight-definition
+ (widget-get
+ parent
+ :hiddenquote-word-number)))))))))
+ (widget-default-notify widget child event)))
+
+(defun hiddenquote-number-action (widget &optional _event)
+ "Check the answer for the word number that widget WIDGET belongs to."
+ (let ((word (nth (widget-get widget :hiddenquote-word-number)
+ (widget-get (widget-get widget :parent) :children)))
+ (inhibit-modification-hooks t))
+ (if (string-match " " (widget-value word))
+ (widget-value-set widget 'unchecked)
+ (widget-value-set widget (if (widget-apply word :validate)
+ 'wrong
+ 'right)))
+ ;; Hack! Restore the cursor-sensor-functions property here,
+ ;; because `widget-value-set' drops it.
+ (add-text-properties (widget-get widget :from)
+ (widget-get widget :to)
+ `(cursor-sensor-functions
+ ,(list
+ (lambda (_window _prev action)
+ (if (eq action 'left)
+ (hiddenquote-unhighlight-definition
+ (widget-get widget
+ :hiddenquote-word-number))
+ (hiddenquote-highlight-definition
+ (widget-get widget
+ :hiddenquote-word-number))
+ (widget-forward 1))))))))
+
+(defun hiddenquote-number-button-face-get (widget)
+ "Return the face to use by the `hiddenquote-number' widget WIDGET."
+ (let ((val (widget-value widget)))
+ (cond ((eq val 'unchecked) 'hiddenquote-word-number)
+ ((eq val 'right) 'hiddenquote-word-right)
+ ((eq val 'wrong) 'hiddenquote-word-wrong)
+ ;; Shouldn't happen.
+ (t 'hiddenquote-word-number))))
+
+(defun hiddenquote-timer-update (widget)
+ "Update `hiddenquote-timer' widget WIDGET."
+ (condition-case nil
+ (with-current-buffer (widget-get widget :hiddenquote-buffer)
+ (save-excursion
+ (goto-char (widget-get widget :from))
+ (let ((elapsed (time-since (widget-get widget :hiddenquote-start))))
+ (setq elapsed (if (fboundp 'time-convert)
+ (time-convert elapsed 'integer)
+ (+ (* 65536 (nth 0 elapsed))
+ (nth 1 elapsed)
+ (* (nth 2 elapsed) 1e-6)
+ (* (nth 3 elapsed) 1e-12))))
+ (widget-value-set widget elapsed)
+ (oset (oref (widget-get hiddenquote-current :hiddenquote) saved)
+ elapsed-time elapsed))))
+ (error (hiddenquote-timer-stop-timer))))
+
+(defun hiddenquote-timer-value-create (widget)
+ "Create the `hiddenquote-timer' widget WIDGET."
+ (let* ((startedp (widget-get widget :hiddenquote-start))
+ (elapsed (if startedp
+ (widget-get widget :value)
+ (if (fboundp 'time-convert) ; Emacs 27.1
+ (time-convert
+ (widget-get widget :hiddenquote-start-value)
+ 'integer)
+ (widget-get widget :hiddenquote-start-value)))))
+ (with-current-buffer (widget-get widget :hiddenquote-buffer)
+ (hiddenquote-insert (format-seconds "%h:%m:%s"
+ (if (numberp elapsed)
+ ;; Probably in Emacs 27.1
+ elapsed
+ ;; Probably in Emacs < 27, so
+ ;; compute it by hand.
+ (+ (* 65536 (nth 0 elapsed))
+ (nth 1 elapsed)
+ (* (nth 2 elapsed) 1e-6)
+ (* (nth 3 elapsed) 1e-12))))
+ (widget-apply widget :sample-face-get)))))
+
+(defun hiddenquote-syllable-value-create (widget)
+ "Create the `hiddenquote-syllable' widget WIDGET."
+ (let ((val (widget-value widget))
+ (text (widget-get widget :tag)))
+ (hiddenquote-insert text (if val
+ 'hiddenquote-used-syllable
+ 'hiddenquote-unused-syllable))))
+
+(defun hiddenquote-syllable-button-face-get (widget)
+ "Return the face to use by the `hiddenquote-syllable' widget WIDGET.
+Return `hiddenquote-used-syllable' if WIDGET's value is non-nil,
+`hiddenquote-unused-syllable' otherwise."
+ (if (widget-value widget)
+ 'hiddenquote-used-syllable
+ 'hiddenquote-unused-syllable))
+
+(defun hiddenquote-syllable-notify (_widget _child &optional _event)
+ "Check if all syllables are marked as used."
+ (when (hiddenquote-puzzle-complete-p)
+ (hiddenquote-timer-stop-timer)))
+
+;; Functions.
+(defun hiddenquote-puzzle-from-ipuz (ipuz)
+ "Return a `hiddenquote-hidden-quote-puzzle' instance specified by IPUZ."
+ (let* ((json (json-read-from-string ipuz))
+ (num (alist-get 'uniqueid json))
+ (author (alist-get 'author json))
+ (editor (alist-get 'editor json))
+ (publisher (alist-get 'publisher json))
+ (copyright (alist-get 'copyright json))
+ (title (alist-get 'title json))
+ (created-date (alist-get 'date json))
+ (desc (alist-get 'intro json))
+ (qquote (alist-get 'answer json))
+ (clues (alist-get 'Clues (alist-get 'clues json)))
+ (words (vconcat
+ (cl-mapcar
+ (lambda (clue)
+ (list (alist-get 'answer clue) (alist-get 'clue clue)))
+ clues)))
+ (arrows (alist-get 'com.hiddenquote:arrows json))
+ (lengths (vconcat (cl-mapcar (lambda (clue)
+ (alist-get 'enumeration clue))
+ clues)))
+ (syllables (alist-get 'com.hiddenquote:syllables json))
+ (saved (let ((spec (alist-get 'saved json)))
+ (make-instance
+ 'hiddenquote-edit
+ :answers (alist-get 'answers spec)
+ :checked-answers (append (alist-get 'checked-answers spec)
+ nil)
+ :used-syllables (append (alist-get 'used-syllables spec)
+ nil)
+ :elapsed-time (or (alist-get 'elapsed-time spec) 0)))))
+ (make-instance 'hiddenquote-hidden-quote-puzzle
+ :id (string-to-number num)
+ :author (or author "")
+ :editor (or editor "")
+ :copyright (or copyright "")
+ :publisher (or publisher "")
+ :quote qquote
+ :description desc
+ :title title
+ :clues words
+ :arrows arrows
+ :created-date created-date
+ :field-lengths lengths
+ :syllables syllables
+ :saved saved)))
+
+(defun hiddenquote-get-local-puzzle (&optional n)
+ "Return a puzzle from this package `puzzles' directory.
+With N non-nil, return that puzzle, otherwise return the newest one."
+ (let* ((num (and n (read-number "Enter a puzzle number: ")))
+ (dir (file-name-directory (locate-library "hiddenquote")))
+ (file (if num
+ (expand-file-name (format "%s.ipuz" num)
+ (file-name-as-directory
+ (expand-file-name "puzzles" dir)))
+ (let* ((files
+ (directory-files
+ (expand-file-name "puzzles"
+ (file-name-directory
+ (locate-library "hiddenquote")))
+ t "[0-9]+.ipuz$" t))
+ (nums
+ (sort (mapcar
+ (lambda (filename)
+ (string-match "\\([0-9]+\\).ipuz$" filename)
+ (string-to-number
+ (match-string 1 filename)))
+ files)
+ #'>)))
+ (expand-file-name (format "%s.ipuz" (car nums))
+ (file-name-as-directory
+ (expand-file-name "puzzles" dir))))))
+ (saved-file (hiddenquote-expand-puzzle-file-name
+ (file-name-nondirectory file) "hidden-quote"))
+ puzzle)
+ (setq puzzle (hiddenquote-puzzle-from-ipuz
+ (with-current-buffer
+ (find-file-noselect (if (file-exists-p saved-file)
+ saved-file
+ file)
+ t)
+ (prog1 (buffer-string)
+ (kill-buffer)))))
+ puzzle))
+
+(defun hiddenquote-get-hidden-quote-puzzle (&optional n)
+ "Return a puzzle from the hidden-quote puzzle source.
+
+With N nil, return the latest puzzle. With N non-nil, return that
+puzzle Nº."
+ (let* ((num (and n (read-number "Enter a puzzle number: ")))
+ (url "http://mauroaranda.com/puzzles/hidden-quote-puzzle/")
+ (file (and num (hiddenquote-expand-puzzle-file-name
+ (format "%s.ipuz" num) "hidden-quote")))
+ puzzle)
+ (if (and file (file-exists-p file)) ; Look for the file locally first.
+ (progn
+ (setq puzzle
+ (hiddenquote-puzzle-from-ipuz
+ (with-current-buffer (find-file-noselect file t)
+ (prog1 (buffer-string)
+ (kill-buffer)))))
+ (oset puzzle file file)
+ puzzle)
+ (and num (setq url (concat url (number-to-string num))))
+ (with-current-buffer (url-retrieve-synchronously url)
+ (goto-char (point-min))
+ (re-search-forward "\n\n")
+ (setq puzzle (hiddenquote-puzzle-from-ipuz
+ (buffer-substring (point) (point-max))))
+ (oset puzzle file (hiddenquote-expand-puzzle-file-name
+ (format "%s.ipuz" (oref puzzle id)) "hidden-quote"))
+ (oset puzzle saved (hiddenquote-load-saved-puzzle puzzle))
+ puzzle))))
+
+(defun hiddenquote-highlight-definition (n)
+ "Highlight the Nth definition."
+ (with-current-buffer (get-buffer (concat (buffer-name) " - Definitions"))
+ (let ((inhibit-read-only t))
+ (set-text-properties (car (aref hiddenquote-definition-markers n))
+ (cdr (aref hiddenquote-definition-markers n))
+ '(face hiddenquote-highlight))
+ (when-let ((w (get-buffer-window (current-buffer))))
+ (set-window-point w (car (aref hiddenquote-definition-markers n)))
+ (or (pos-visible-in-window-p
+ (cdr (aref hiddenquote-definition-markers n)) w)
+ (with-selected-window w
+ (recenter)))))))
+
+(defun hiddenquote-unhighlight-definition (n)
+ "Unhighlight the Nth definition."
+ (with-current-buffer (get-buffer (concat (buffer-name) " - Definitions"))
+ (let ((inhibit-read-only t))
+ (set-text-properties (car (aref hiddenquote-definition-markers n))
+ (cdr (aref hiddenquote-definition-markers n))
+ '(face default)))))
+
+(defun hiddenquote-timer-start-timer (widget)
+ "Start the `hiddenquote-timer' WIDGET."
+ (unless (widget-get widget :hiddenquote-start) ; Alreay started.
+ (let ((elapsed (if (fboundp 'time-convert) ; Emacs 27.1
+ (time-convert
+ (widget-get widget :hiddenquote-start-value)
+ 'integer)
+ (widget-get widget :hiddenquote-start-value)))
+ (timer (run-with-timer 1 1 (lambda ()
+ (hiddenquote-timer-update widget)))))
+ (widget-put widget :hiddenquote-start
+ (time-subtract (current-time) elapsed))
+ (widget-put widget :hiddenquote-timer timer)
+ (add-hook 'kill-buffer-hook #'hiddenquote-timer-stop-timer nil t))))
+
+(defun hiddenquote-timer-stop-timer ()
+ "Stop the hiddenquote timer."
+ (when hiddenquote-buffer
+ (with-current-buffer hiddenquote-buffer
+ (let ((timer (widget-get
+ (widget-get hiddenquote-current :hiddenquote-timer)
+ :hiddenquote-timer)))
+ (when (timerp timer)
+ (cancel-timer timer))))))
+
+(defun hiddenquote-puzzle-complete-p ()
+ "Non-nil if the grid is complete."
+ (when-let ((used-all-p (with-current-buffer
+ (concat (buffer-name hiddenquote-buffer)
+ " - Syllables")
+ (cl-every #'widget-value hiddenquote-syllables))))
+ (with-current-buffer hiddenquote-buffer
+ (cl-notany (lambda (w) (widget-apply w :validate))
+ (widget-get hiddenquote-current :children)))))
+
+(defun hiddenquote-initialize ()
+ "Initialize variables and modes needed by `hiddenquote'."
+ (erase-buffer)
+ (remove-overlays)
+ (setq hiddenquote-previous-window-configuration
+ (current-window-configuration))
+ (delete-other-windows)
+ (cursor-sensor-mode 1)
+ (setq widget-documentation-face 'hiddenquote-doc)
+ (setq truncate-lines t)
+ ;; Compatibility.
+ (when (< emacs-major-version 28)
+ (add-hook 'after-change-functions #'hiddenquote-after-change nil t)
+ (add-hook 'widget-backward-hook #'hiddenquote-widget-backward nil t)))
+
+;; Hiddenquote mode.
+(easy-menu-define hiddenquote-menu (list hiddenquote-mode-map
+ hiddenquote-character-map)
+ "Menu for hiddenquote."
+ '("Hiddenquote"
+ ["Automatic checking" hiddenquote-toggle-automatic-check
+ :style radio :selected hiddenquote-automatic-check]
+ ["Manual checking" hiddenquote-toggle-automatic-check
+ :style radio :selected (not hiddenquote-automatic-check)]
+ ["Give up" hiddenquote-give-up]
+ ["Save progress" hiddenquote-save]
+ ["Quit" hiddenquote-quit]))
+
+(easy-menu-define hiddenquote-character-menu hiddenquote-character-map
+ "Menu for hiddenquote, when inside a character cell."
+ '("Hiddenquote"
+ ["Check answer" hiddenquote-check-answer]))
+
+(defvar hiddenquote-tool-bar-map
+ (let ((map (make-sparse-keymap)))
+ (tool-bar-local-item-from-menu #'hiddenquote-give-up "cancel" map
+ hiddenquote-mode-map)
+ (tool-bar-local-item-from-menu #'hiddenquote-save "save" map
+ hiddenquote-mode-map)
+ (tool-bar-local-item-from-menu #'hiddenquote-quit "exit" map
+ hiddenquote-mode-map)
+ (define-key-after map [sep] menu-bar-separator)
+ (tool-bar-local-item-from-menu #'hiddenquote-check-answer "search" map
+ hiddenquote-character-map)
+ map))
+
+(define-derived-mode hiddenquote-mode nil "Hiddenquote"
+ "Major mode for `hiddenquote'.
+
+Buffer bindings:
+\\{hiddenquote-mode-map}
+
+Character cell bindings:
+\\{hiddenquote-character-map}."
+ (when (boundp 'tool-bar-map)
+ (setq-local tool-bar-map hiddenquote-tool-bar-map)))
+
+;; Commands.
+(defun hiddenquote-kill-word ()
+ "Starting at point, delete the rest of the characters of the current word."
+ (interactive)
+ (let* ((child (widget-at))
+ (parent (widget-get (widget-at) :parent)))
+ (save-excursion
+ (dolist (w (member child (widget-get parent :children)))
+ (widget-value-set w "")))))
+
+(defun hiddenquote-kill-whole-word ()
+ "Delete each character in a word."
+ (interactive)
+ (let ((parent (widget-get (widget-at) :parent)))
+ (save-excursion
+ (dolist (child (widget-get parent :children))
+ (widget-value-set child "")))))
+
+(defun hiddenquote-delete-backward-char ()
+ "Delete the previous character."
+ (interactive)
+ (widget-backward 1)
+ (unless (eq (widget-type (widget-at)) 'hiddenquote-character)
+ (widget-forward 1))
+ (delete-char 1))
+
+(defun hiddenquote-forward ()
+ "Go to the next character if at a widget, use `forward-char' otherwise."
+ (interactive)
+ (if (widget-at)
+ (progn
+ (widget-forward 1)
+ (while (not (eq (widget-type (widget-at)) 'hiddenquote-character))
+ (widget-forward 1)))
+ (forward-char)))
+
+(defun hiddenquote-backward ()
+ "Go to the previous character if at a widget, use `backward-char' otherwise."
+ (interactive)
+ (if (widget-at)
+ (progn
+ (widget-backward 1)
+ (while (not (eq (widget-type (widget-at)) 'hiddenquote-character))
+ (widget-backward 1)))
+ (backward-char)))
+
+(defun hiddenquote-next ()
+ "Go to the next word if at a widget, to the next line otherwise."
+ (interactive)
+ (if (widget-at)
+ (progn
+ (forward-line)
+ (beginning-of-line)
+ (widget-forward 1)
+ (while (not (eq (widget-type (widget-at)) 'hiddenquote-character))
+ (widget-forward 1)))
+ (forward-line)))
+
+(defun hiddenquote-prev ()
+ "Go to the previous word if at a widget, to the previous line otherwise."
+ (interactive)
+ (if (widget-at)
+ (progn
+ (forward-line -2)
+ (beginning-of-line)
+ (widget-forward 1)
+ (while (not (eq (widget-type (widget-at)) 'hiddenquote-character))
+ (widget-forward 1)))
+ (forward-line -1)))
+
+(defun hiddenquote-move-beginning-of-word ()
+ "Go to the first character of the word point is at."
+ (interactive)
+ (forward-line 0)
+ (widget-forward 1))
+
+(defun hiddenquote-move-end-of-word ()
+ "Go to the last character of the word point is at."
+ (interactive)
+ (let ((inhibit-field-text-motion t))
+ (end-of-line)
+ (widget-backward 1)))
+
+(defun hiddenquote-goto-word (n)
+ "Go to the Nth word."
+ (interactive (list (read-number "Goto word: ")))
+ (if (or (>= (1- n) (length (widget-get hiddenquote-current :buttons)))
+ (< (1- n) 0))
+ (user-error "Invalid word number")
+ (goto-char (widget-get
+ (nth (1- n) (widget-get hiddenquote-current :buttons))
+ :from))))
+
+(defun hiddenquote-forward-syllable ()
+ "Move N syllables forward."
+ (interactive)
+ (if hiddenquote-skip-used-syllables
+ (let ((orig (widget-at)))
+ (widget-forward 1)
+ (while (and (widget-get (widget-at) :value)
+ (not (eq orig (widget-at))))
+ (widget-forward 1)))
+ (widget-forward 1)))
+
+(defun hiddenquote-backward-syllable ()
+ "Move N syllables backward."
+ (interactive)
+ (if hiddenquote-skip-used-syllables
+ (let ((orig (widget-at)))
+ (widget-backward 1)
+ (while (and (widget-get (widget-at) :value)
+ (not (eq orig (widget-at))))
+ (widget-backward 1)))
+ (widget-backward 1)))
+
+(defun hiddenquote-check-answer ()
+ "Check if the answer for the word point is at is right or wrong."
+ (interactive)
+ (let ((parent (widget-get (widget-at) :parent)))
+ (widget-apply-action (nth (widget-get parent :hiddenquote-word-number)
+ (widget-get (widget-get parent :parent)
+ :buttons)))))
+
+(defun hiddenquote-toggle-automatic-check ()
+ "Toggle the `hiddenquote-automatic-check' variable."
+ (interactive)
+ (customize-set-variable 'hiddenquote-automatic-check
+ (not hiddenquote-automatic-check)))
+
+(defun hiddenquote-save ()
+ "Save the puzzle and the user progress."
+ (interactive)
+ (let* ((puzzle (widget-get hiddenquote-current :hiddenquote))
+ (user-answers (vconcat
+ (mapcar #'widget-value
+ (widget-get hiddenquote-current :children))))
+ (checked-answers (mapcar #'widget-value
+ (widget-get hiddenquote-current :buttons)))
+ (used-syllables
+ (with-current-buffer (concat (buffer-name) " - Syllables")
+ (cl-loop for widget in hiddenquote-syllables
+ for i = 0 then (1+ i)
+ when (widget-value widget)
+ collect i)))
+ (elapsed-time
+ (when (widget-get hiddenquote-current :hiddenquote-timer)
+ (widget-value
+ (widget-get hiddenquote-current :hiddenquote-timer))))
+ (edits (make-instance 'hiddenquote-edit
+ :answers user-answers
+ :checked-answers checked-answers
+ :used-syllables used-syllables
+ :elapsed-time elapsed-time)))
+ (oset puzzle saved edits)
+ (with-temp-buffer
+ (insert (hiddenquote-puzzle-to-ipuz puzzle))
+ (hiddenquote-ensure-file-exists (oref puzzle file))
+ (write-file (oref puzzle file)))
+ (message "Saved puzzle in %s" (oref puzzle file))))
+
+(defun hiddenquote-give-up ()
+ "Give up completing the puzzle, showing the solutions."
+ (interactive)
+ (when (yes-or-no-p "Really give up? ")
+ (save-excursion
+ (with-current-buffer (concat
+ (buffer-name hiddenquote-buffer) " - Syllables")
+ (dolist (syllable hiddenquote-syllables)
+ (widget-value-set syllable t)))
+ (let ((i 0)
+ (grid hiddenquote-current))
+ (dolist (child (widget-get grid :children))
+ (widget-value-set
+ child (car (aref (oref (widget-get grid :hiddenquote) clues) i)))
+ (setq i (1+ i))))
+ (hiddenquote-timer-stop-timer))))
+
+(defun hiddenquote-quit ()
+ "Prompt the user and kill the buffer if the answer is \"yes\"."
+ (interactive)
+ (when (yes-or-no-p "Really quit playing Hiddenquote? ")
+ (hiddenquote-timer-stop-timer)
+ (let ((name (buffer-name)))
+ (dolist (buff (mapcar (lambda (suffix)
+ (concat name suffix))
+ '(" - Definitions" " - Syllables")))
+ (when (get-buffer buff)
+ (with-current-buffer buff
+ (setq-local kill-buffer-hook nil)
+ (kill-buffer))))
+ (kill-buffer))
+ (set-window-configuration hiddenquote-previous-window-configuration)))
+
+;;;###autoload
+(defun hiddenquote (&optional arg)
+ "Start playing a Hidden Quote puzzle.
+
+If there's only one element in `hiddenquote-sources', choose that source.
+Else, prompt the user with the options in `hiddenquote-sources',
+to get the puzzle to play.
+ARG is passed along to the function that will get the puzzle. To find out
+how ARG is handled, read the documentation for the publisher function in
+`hiddenquote-sources'.
+
+Returns the buffer with the puzzle."
+ (interactive "P")
+ (let* ((source-fn (if (nthcdr 1 hiddenquote-sources)
+ (widget-choose "Choose a source"
+ (mapcar (lambda (source)
+ (cons (nth 0 source)
+ (nth 2 source)))
+ hiddenquote-sources))
+ (nth 2 (car hiddenquote-sources))))
+ (puzzle (funcall source-fn arg))
+ (buff (get-buffer-create (hiddenquote-format-buffer-name puzzle))))
+ (switch-to-buffer buff)
+ (hiddenquote-mode)
+ (hiddenquote-initialize)
+ ;; Local variables after initializing, otherwise they get killed.
+ (setq hiddenquote-buffer buff)
+ (setq hiddenquote-current (hiddenquote-create-grid puzzle))
+ ;; Wrap up.
+ (widget-setup)
+ (current-buffer)))
+
+(provide 'hiddenquote)
+;;; hiddenquote.el ends here
diff --git a/puzzles/1.ipuz b/puzzles/1.ipuz
new file mode 100644
index 0000000..30e2728
--- /dev/null
+++ b/puzzles/1.ipuz
@@ -0,0 +1,947 @@
+{
+ "version": "http://ipuz.org/v2",
+ "kind": [
+ "http://mauroaranda.com/puzzles/hiddenquote#1",
+ "http://ipuz.org/crossword#1"
+ ],
+ "copyright": "Copyright (C) 2021 tbb",
+ "publisher": "MauroAranda",
+ "url": "http://mauroaranda.com/puzzles/hidden-quote-puzzle/1",
+ "uniqueid": "1",
+ "title": "Hidden Puzzle Nº1",
+ "author": "Mauro Aranda",
+ "editor": "Mauro Aranda",
+ "date": "01/15/2021",
+ "origin": "GNU Emacs",
+ "intro": "Complete the grid to find a quote from Vincent Van Gogh.",
+ "dimensions": {
+ "width": 13,
+ "height": 35
+ },
+ "puzzle": [
+ [
+ "1",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "2",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null
+ ],
+ [
+ "3",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "4",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "5",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "6",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "7",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "8",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "9",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "10",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "11",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "12",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "13",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "14",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "15",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "16",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "17",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "18",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "19",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null
+ ],
+ [
+ "20",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "21",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "22",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "23",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "24",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "25",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "26",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "27",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null
+ ],
+ [
+ "28",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "29",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "30",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "31",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "32",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "33",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "34",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "35",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ]
+ ],
+ "clues": {
+ "Clues": [
+ {
+ "number": 1,
+ "clue": "A man well born; one of good family; one above the condition
of a yeoman",
+ "answer": "gentleman",
+ "enumeration": 9
+ },
+ {
+ "number": 2,
+ "clue": "A transformation that can be applied to an image to prepare
it for printing",
+ "answer": "rasterising",
+ "enumeration": 11
+ },
+ {
+ "number": 3,
+ "clue": "To bottle",
+ "answer": "embottle",
+ "enumeration": 8
+ },
+ {
+ "number": 4,
+ "clue": "A muscle which serves to draw a part out, or form the median
line of the body",
+ "answer": "abductor",
+ "enumeration": 8
+ },
+ {
+ "number": 5,
+ "clue": "(Geom.) A plane four-sided figure, having two sides parallel
to each other",
+ "answer": "trapezoid",
+ "enumeration": 9
+ },
+ {
+ "number": 6,
+ "clue": "A salt of tartaric acid",
+ "answer": "tartrate",
+ "enumeration": 8
+ },
+ {
+ "number": 7,
+ "clue": "Without a helm or rudder",
+ "answer": "helmless",
+ "enumeration": 8
+ },
+ {
+ "number": 8,
+ "clue": "The act of including, or the state of being included",
+ "answer": "inclusion",
+ "enumeration": 9
+ },
+ {
+ "number": 9,
+ "clue": "Full of neglect, careless",
+ "answer": "neglectful",
+ "enumeration": 10
+ },
+ {
+ "number": 10,
+ "clue": "A musician who plays the guitar",
+ "answer": "guitarist",
+ "enumeration": 9
+ },
+ {
+ "number": 11,
+ "clue": "The opinions and maxims of the Stoics",
+ "answer": "stoicism",
+ "enumeration": 8
+ },
+ {
+ "number": 12,
+ "clue": "The palace of the Moorish kings at Granada",
+ "answer": "alhambra",
+ "enumeration": 8
+ },
+ {
+ "number": 13,
+ "clue": "A fall or descent of rain",
+ "answer": "rainfall",
+ "enumeration": 8
+ },
+ {
+ "number": 14,
+ "clue": "Outward, exterior",
+ "answer": "external",
+ "enumeration": 8
+ },
+ {
+ "number": 15,
+ "clue": "Next in order after the eighty-ninth",
+ "answer": "ninetieth",
+ "enumeration": 9
+ },
+ {
+ "number": 16,
+ "clue": "During the night",
+ "answer": "overnight",
+ "enumeration": 9
+ },
+ {
+ "number": 17,
+ "clue": "The act of forming into a table or tables",
+ "answer": "tabluation",
+ "enumeration": 10
+ },
+ {
+ "number": 18,
+ "clue": "The state of becoming gradually less",
+ "answer": "decrement",
+ "enumeration": 9
+ },
+ {
+ "number": 19,
+ "clue": "An autographic record made by an oscillograph",
+ "answer": "oscillogram",
+ "enumeration": 11
+ },
+ {
+ "number": 20,
+ "clue": "The point between the north and east, at an equal distance
from each",
+ "answer": "northeast",
+ "enumeration": 9
+ },
+ {
+ "number": 21,
+ "clue": "Capable of being expanded or spread out widely",
+ "answer": "expansible",
+ "enumeration": 10
+ },
+ {
+ "number": 22,
+ "clue": "A U.S. city, in Maryland",
+ "answer": "Baltimore",
+ "enumeration": 9
+ },
+ {
+ "number": 23,
+ "clue": "Orangeroot; An American ranunculaceous plant, having a yellow
tuberous root",
+ "answer": "yellowroot",
+ "enumeration": 10
+ },
+ {
+ "number": 24,
+ "clue": "Of or pertaining to one only",
+ "answer": "individual",
+ "enumeration": 10
+ },
+ {
+ "number": 25,
+ "clue": "Long and tedious talk without much substance; superfluity of
words",
+ "answer": "macrology",
+ "enumeration": 9
+ },
+ {
+ "number": 26,
+ "clue": "Somewhat purple",
+ "answer": "purplish",
+ "enumeration": 8
+ },
+ {
+ "number": 27,
+ "clue": "Not used; not habituated; unfamiliar",
+ "answer": "unaccostumed",
+ "enumeration": 12
+ },
+ {
+ "number": 28,
+ "clue": "An edifice or place full of intricate passageways which
render it difficult to find the way from the interior to the entrance",
+ "answer": "labyrinth",
+ "enumeration": 9
+ },
+ {
+ "number": 29,
+ "clue": "Cane sugar",
+ "answer": "saccharose",
+ "enumeration": 10
+ },
+ {
+ "number": 30,
+ "clue": "To give courage to",
+ "answer": "encourage",
+ "enumeration": 9
+ },
+ {
+ "number": 31,
+ "clue": "Cooked on a barbecue",
+ "answer": "barbecued",
+ "enumeration": 9
+ },
+ {
+ "number": 32,
+ "clue": "One who pushes a principle or measure to extremes; an
extremist",
+ "answer": "ultraist",
+ "enumeration": 8
+ },
+ {
+ "number": 33,
+ "clue": "Pain in a tooth or in the teeth",
+ "answer": "toothache",
+ "enumeration": 9
+ },
+ {
+ "number": 34,
+ "clue": "Firmness; moral principle; steadfastness",
+ "answer": "backbone",
+ "enumeration": 8
+ },
+ {
+ "number": 35,
+ "clue": " - terrier: Very small breed, having a long glossy coat of
bluish-grey and tan",
+ "answer": "yorkshire",
+ "enumeration": 9
+ }
+ ]
+ },
+ "answer": "Great things are not done by impulse, but by a series of small
things brought together",
+ "zones": [
+ {
+ "rect": [
+ 0,
+ 0,
+ 0,
+ 34
+ ],
+ "style": {
+ "border": 0
+ }
+ },
+ {
+ "rect": [
+ 1,
+ 0,
+ 1,
+ 34
+ ],
+ "style": {
+ "highlight": true
+ }
+ },
+ {
+ "rect": [
+ 8,
+ 0,
+ 8,
+ 34
+ ],
+ "style": {
+ "highlight": true
+ }
+ }
+ ],
+ "volatile": {
+ "com.hiddenquote:arrows": "",
+ "com.hiddenquote:subject": "",
+ "com.hiddenquote:syllables": ""
+ },
+ "com.hiddenquote:arrows": "1,8",
+ "com.hiddenquote:subject": "General",
+ "com.hiddenquote:syllables": [
+ "ab",
+ "ac",
+ "ache",
+ "age",
+ "al",
+ "al",
+ "back",
+ "bal",
+ "bar",
+ "be",
+ "ble",
+ "bo",
+ "bone",
+ "bra",
+ "cha",
+ "cil",
+ "cism",
+ "clu",
+ "cour",
+ "crol",
+ "cued",
+ "cus",
+ "dec",
+ "di",
+ "duc",
+ "e",
+ "east",
+ "em",
+ "en",
+ "eth",
+ "ex",
+ "ex",
+ "fall",
+ "ful",
+ "gen",
+ "gram",
+ "gui",
+ "gy",
+ "ham",
+ "helm",
+ "i",
+ "in",
+ "in",
+ "ist",
+ "ist",
+ "la",
+ "lab",
+ "lect",
+ "less",
+ "lo",
+ "low",
+ "ma",
+ "man",
+ "ment",
+ "more",
+ "nal",
+ "neg",
+ "night",
+ "nine",
+ "north",
+ "o",
+ "o",
+ "os",
+ "pan",
+ "plish",
+ "pur",
+ "rain",
+ "ras",
+ "re",
+ "ri",
+ "rinth",
+ "root",
+ "rose",
+ "sac",
+ "shire",
+ "si",
+ "sing",
+ "sion",
+ "sto",
+ "tab",
+ "tar",
+ "tar",
+ "te",
+ "ter",
+ "ti",
+ "ti",
+ "tion",
+ "tle",
+ "tle",
+ "tomed",
+ "tooth",
+ "tor",
+ "tra",
+ "trap",
+ "trate",
+ "u",
+ "u",
+ "ul",
+ "un",
+ "ver",
+ "vid",
+ "y",
+ "yel",
+ "york",
+ "zoid"
+ ]
+}
diff --git a/puzzles/2.ipuz b/puzzles/2.ipuz
new file mode 100644
index 0000000..6d970b3
--- /dev/null
+++ b/puzzles/2.ipuz
@@ -0,0 +1,532 @@
+{
+ "version": "http://ipuz.org/v2",
+ "kind": [
+ "http://mauroaranda.com/puzzles/hiddenquote#1",
+ "http://ipuz.org/crossword#1"
+ ],
+ "copyright": "Copyright (C) 2021 Mauro Aranda",
+ "publisher": "MauroAranda",
+ "url": "http://mauroaranda.com/puzzles/hidden-quote-puzzle/2",
+ "uniqueid": "2",
+ "title": "Grid Nº2",
+ "author": "Mauro Aranda",
+ "editor": "Mauro Aranda",
+ "date": "01/17/2021",
+ "origin": "GNU Emacs",
+ "intro": "Find a quote from Ovid.",
+ "dimensions": {
+ "width": 15,
+ "height": 17
+ },
+ "puzzle": [
+ [
+ "1",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "2",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "3",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "4",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "5",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "6",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "7",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null
+ ],
+ [
+ "8",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "9",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "10",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "11",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "12",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "13",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "14",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "15",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "16",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "17",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ]
+ ],
+ "clues": {
+ "Clues": [
+ {
+ "number": 1,
+ "clue": "Loosed from any limitation or condition",
+ "answer": "absolute",
+ "enumeration": 8
+ },
+ {
+ "number": 2,
+ "clue": "Without a name",
+ "answer": "nameless",
+ "enumeration": 8
+ },
+ {
+ "number": 3,
+ "clue": "A former country of southeastern Europe bordering the
Adriatic Sea",
+ "answer": "yugoslavia",
+ "enumeration": 10
+ },
+ {
+ "number": 4,
+ "clue": "One who teaches or instructs",
+ "answer": "teacher",
+ "enumeration": 7
+ },
+ {
+ "number": 5,
+ "clue": "Of or pertaining to hydraulics, or to fluids in motion",
+ "answer": "hydraulic",
+ "enumeration": 9
+ },
+ {
+ "number": 6,
+ "clue": "The state or quality of being identical, or the same",
+ "answer": "identity",
+ "enumeration": 8
+ },
+ {
+ "number": 7,
+ "clue": "The manipulation or construction of objects with sizes in the
nanometer range or smaller",
+ "answer": "nanotechnology",
+ "enumeration": 14
+ },
+ {
+ "number": 8,
+ "clue": "A piece of ground appropriated to the cultivation of herbs,
fruits, flowers, or vegetables",
+ "answer": "garden",
+ "enumeration": 6
+ },
+ {
+ "number": 9,
+ "clue": "An orthopterous insect of the genus Blatta, and allied
genera",
+ "answer": "cockroach",
+ "enumeration": 9
+ },
+ {
+ "number": 10,
+ "clue": "Violent or riotous behavior",
+ "answer": "rampage",
+ "enumeration": 7
+ },
+ {
+ "number": 11,
+ "clue": "The act of adding two or more things together",
+ "answer": "addition",
+ "enumeration": 8
+ },
+ {
+ "number": 12,
+ "clue": "Someone injured or killed or captured or missing in a
military engagement",
+ "answer": "casualty",
+ "enumeration": 8
+ },
+ {
+ "number": 13,
+ "clue": "Chemical element, symbol Kr",
+ "answer": "krypton",
+ "enumeration": 7
+ },
+ {
+ "number": 14,
+ "clue": "One employed by another",
+ "answer": "employee",
+ "enumeration": 8
+ },
+ {
+ "number": 15,
+ "clue": "That causes disgust",
+ "answer": "disgusting",
+ "enumeration": 10
+ },
+ {
+ "number": 16,
+ "clue": "A person who cuts wood",
+ "answer": "woodcutter",
+ "enumeration": 10
+ },
+ {
+ "number": 17,
+ "clue": "A man born in Ireland or of the Irish race",
+ "answer": "irishman",
+ "enumeration": 8
+ }
+ ]
+ },
+ "answer": "Anything cracked will shatter at a touch",
+ "zones": [
+ {
+ "rect": [
+ 0,
+ 0,
+ 0,
+ 16
+ ],
+ "style": {
+ "border": 0
+ }
+ },
+ {
+ "rect": [
+ 1,
+ 0,
+ 1,
+ 16
+ ],
+ "style": {
+ "highlight": true
+ }
+ },
+ {
+ "rect": [
+ 5,
+ 0,
+ 5,
+ 16
+ ],
+ "style": {
+ "highlight": true
+ }
+ }
+ ],
+ "volatile": {
+ "com.hiddenquote:arrows": "",
+ "com.hiddenquote:subject": "",
+ "com.hiddenquote:syllables": ""
+ },
+ "com.hiddenquote:arrows": "1,5",
+ "com.hiddenquote:subject": "general",
+ "com.hiddenquote:syllables": [
+ "a",
+ "ab",
+ "ad",
+ "age",
+ "al",
+ "cas",
+ "cock",
+ "cut",
+ "den",
+ "den",
+ "di",
+ "dis",
+ "drau",
+ "ee",
+ "em",
+ "er",
+ "gar",
+ "gos",
+ "gust",
+ "gy",
+ "hy",
+ "i",
+ "i",
+ "ing",
+ "kryp",
+ "la",
+ "less",
+ "lic",
+ "lute",
+ "man",
+ "name",
+ "nan",
+ "nol",
+ "o",
+ "o",
+ "ploy",
+ "ramp",
+ "rish",
+ "roach",
+ "so",
+ "teach",
+ "tech",
+ "ter",
+ "ti",
+ "tion",
+ "ton",
+ "ty",
+ "ty",
+ "u",
+ "vi",
+ "wood",
+ "yu"
+ ]
+}
diff --git a/puzzles/3.ipuz b/puzzles/3.ipuz
new file mode 100644
index 0000000..6abd8cc
--- /dev/null
+++ b/puzzles/3.ipuz
@@ -0,0 +1,798 @@
+{
+ "version": "http://ipuz.org/v2",
+ "kind": [
+ "http://mauroaranda.com/puzzles/hiddenquote#1",
+ "http://ipuz.org/crossword#1"
+ ],
+ "copyright": "Copyright (C) 2021 Mauro Aranda",
+ "publisher": "MauroAranda",
+ "url": "http://mauroaranda.com/puzzles/hidden-quote-puzzle/3",
+ "uniqueid": "3",
+ "title": "Grid Nº3",
+ "author": "Mauro Aranda",
+ "editor": "Mauro Aranda",
+ "date": "01/19/2021",
+ "origin": "GNU Emacs",
+ "intro": "Complete the grid to find a quote from Blaise Pascal",
+ "dimensions": {
+ "width": 14,
+ "height": 28
+ },
+ "puzzle": [
+ [
+ "1",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "2",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null
+ ],
+ [
+ "3",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "4",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "5",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "6",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "7",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "8",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "9",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "10",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "11",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "12",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "13",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "14",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "15",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "16",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "17",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "18",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "19",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "20",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "21",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "22",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "23",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "24",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "25",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "26",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "27",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "28",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ]
+ ],
+ "clues": {
+ "Clues": [
+ {
+ "number": 1,
+ "clue": "(Li) A metallic element of the alkaline group",
+ "answer": "lithium",
+ "enumeration": 7
+ },
+ {
+ "number": 2,
+ "clue": "Mental disorder in which people interpret reality abnormally",
+ "answer": "schizophrenia",
+ "enumeration": 13
+ },
+ {
+ "number": 3,
+ "clue": "A brother by the marriage of one's parent with the parent of
another",
+ "answer": "stepbrother",
+ "enumeration": 11
+ },
+ {
+ "number": 4,
+ "clue": "A temperature scale",
+ "answer": "celsius",
+ "enumeration": 7
+ },
+ {
+ "number": 5,
+ "clue": "- steroid. Any of a variety of synthetic deriatives or
analogs of testoterone, which promote the increase of muscle mass and strength",
+ "answer": "anabolic",
+ "enumeration": 8
+ },
+ {
+ "number": 6,
+ "clue": "To hinder; to withhold",
+ "answer": "abstain",
+ "enumeration": 7
+ },
+ {
+ "number": 7,
+ "clue": "(Massicot) Lead monoxide, used in making flint glass",
+ "answer": "litharge",
+ "enumeration": 8
+ },
+ {
+ "number": 8,
+ "clue": "A fot and moist substance applied externally to some part of
the body",
+ "answer": "cataplasm",
+ "enumeration": 9
+ },
+ {
+ "number": 9,
+ "clue": "Lessons done in class or assigned to be done at home",
+ "answer": "schoolwork",
+ "enumeration": 10
+ },
+ {
+ "number": 10,
+ "clue": "U.S. city, in Illinois",
+ "answer": "Chicago",
+ "enumeration": 7
+ },
+ {
+ "number": 11,
+ "clue": "An event or accomplishment of great significance",
+ "answer": "landmark",
+ "enumeration": 8
+ },
+ {
+ "number": 12,
+ "clue": "A book in which a log is recorded",
+ "answer": "logbook",
+ "enumeration": 7
+ },
+ {
+ "number": 13,
+ "clue": "Pertaining to the sense of hearing, the organs of hearing, or
the science of sounds",
+ "answer": "acoustic",
+ "enumeration": 8
+ },
+ {
+ "number": 14,
+ "clue": "The office or power of a censor",
+ "answer": "censorship",
+ "enumeration": 10
+ },
+ {
+ "number": 15,
+ "clue": "The brow above the eye",
+ "answer": "eyebrow",
+ "enumeration": 7
+ },
+ {
+ "number": 16,
+ "clue": "(The -). One of Francis Ford Coppola movies",
+ "answer": "godfather",
+ "enumeration": 9
+ },
+ {
+ "number": 17,
+ "clue": "A small writing board with a clip attached at the top for
holding papers",
+ "answer": "clipboard",
+ "enumeration": 9
+ },
+ {
+ "number": 18,
+ "clue": "One of the West Goths",
+ "answer": "visigoth",
+ "enumeration": 8
+ },
+ {
+ "number": 19,
+ "clue": "A cubic volume of ten liters",
+ "answer": "decaliter",
+ "enumeration": 9
+ },
+ {
+ "number": 20,
+ "clue": "The pulpy fruit of a tree of tropical America",
+ "answer": "avocado",
+ "enumeration": 7
+ },
+ {
+ "number": 21,
+ "clue": "One who, or that which, develops",
+ "answer": "developer",
+ "enumeration": 9
+ },
+ {
+ "number": 22,
+ "clue": "A veteran who has honorably completed his service",
+ "answer": "emeritus",
+ "enumeration": 8
+ },
+ {
+ "number": 23,
+ "clue": "One who practices rope dancing, high vaulting, or other
daring gymnastic feats",
+ "answer": "acrobat",
+ "enumeration": 7
+ },
+ {
+ "number": 24,
+ "clue": "Meritorious",
+ "answer": "desertful",
+ "enumeration": 9
+ },
+ {
+ "number": 25,
+ "clue": "Merely imaginary; fanciful; fantastic",
+ "answer": "chimerical",
+ "enumeration": 10
+ },
+ {
+ "number": 26,
+ "clue": "A large dark-colored variety of the leopard",
+ "answer": "panther",
+ "enumeration": 7
+ },
+ {
+ "number": 27,
+ "clue": "The wife of a duke",
+ "answer": "duchess",
+ "enumeration": 7
+ },
+ {
+ "number": 28,
+ "clue": "An engraving on wood",
+ "answer": "woodcut",
+ "enumeration": 7
+ }
+ ]
+ },
+ "answer": "The last thing one discovers in composing a work is what to put
first",
+ "zones": [
+ {
+ "rect": [
+ 0,
+ 0,
+ 0,
+ 27
+ ],
+ "style": {
+ "border": 0
+ }
+ },
+ {
+ "rect": [
+ 3,
+ 0,
+ 3,
+ 27
+ ],
+ "style": {
+ "highlight": true
+ }
+ },
+ {
+ "rect": [
+ 7,
+ 0,
+ 7,
+ 27
+ ],
+ "style": {
+ "highlight": true
+ }
+ }
+ ],
+ "volatile": {
+ "com.hiddenquote:arrows": "",
+ "com.hiddenquote:subject": "",
+ "com.hiddenquote:syllables": ""
+ },
+ "com.hiddenquote:arrows": "3,7",
+ "com.hiddenquote:subject": "general",
+ "com.hiddenquote:syllables": [
+ "a",
+ "a",
+ "a",
+ "a",
+ "a",
+ "ab",
+ "ac",
+ "al",
+ "an",
+ "arge",
+ "av",
+ "bat",
+ "board",
+ "bol",
+ "book",
+ "broth",
+ "brow",
+ "ca",
+ "ca",
+ "cat",
+ "cel",
+ "cen",
+ "chi",
+ "chi",
+ "clip",
+ "cous",
+ "cut",
+ "de",
+ "de",
+ "dec",
+ "do",
+ "duch",
+ "e",
+ "er",
+ "er",
+ "ess",
+ "eye",
+ "fa",
+ "ful",
+ "go",
+ "god",
+ "goth",
+ "i",
+ "i",
+ "i",
+ "ic",
+ "ic",
+ "land",
+ "li",
+ "lith",
+ "lith",
+ "log",
+ "mark",
+ "mer",
+ "mer",
+ "ni",
+ "o",
+ "o",
+ "op",
+ "pan",
+ "phre",
+ "plasm",
+ "ro",
+ "schiz",
+ "school",
+ "sert",
+ "ship",
+ "si",
+ "sor",
+ "stain",
+ "step",
+ "ter",
+ "ther",
+ "ther",
+ "tic",
+ "tus",
+ "um",
+ "us",
+ "vel",
+ "vis",
+ "wood",
+ "work"
+ ]
+}
diff --git a/puzzles/4.ipuz b/puzzles/4.ipuz
new file mode 100644
index 0000000..feb5394
--- /dev/null
+++ b/puzzles/4.ipuz
@@ -0,0 +1,532 @@
+{
+ "version": "http://ipuz.org/v2",
+ "kind": [
+ "http://mauroaranda.com/puzzles/hiddenquote#1",
+ "http://ipuz.org/crossword#1"
+ ],
+ "copyright": "Copyright (C) 2021 tbb",
+ "publisher": "MauroAranda",
+ "url": "http://mauroaranda.com/puzzles/hidden-quote-puzzle/1",
+ "uniqueid": "4",
+ "title": "Hidden Puzzle Nº1",
+ "author": "Mauro Aranda",
+ "editor": "Mauro Aranda",
+ "date": "01/26/2021",
+ "origin": "GNU Emacs",
+ "intro": "Complete the grid to read an Aristotle quote",
+ "dimensions": {
+ "width": 11,
+ "height": 20
+ },
+ "puzzle": [
+ [
+ "1",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "2",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "3",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "4",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "5",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "6",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "7",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "8",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "9",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null
+ ],
+ [
+ "10",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "11",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null
+ ],
+ [
+ "12",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "13",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "14",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "15",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null
+ ],
+ [
+ "16",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null
+ ],
+ [
+ "17",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null
+ ],
+ [
+ "18",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null
+ ],
+ [
+ "19",
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ null
+ ],
+ [
+ "20",
+ 0,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ]
+ ],
+ "clues": {
+ "Clues": [
+ {
+ "number": 1,
+ "clue": "(Arch.) A low wall, especially one serving to protect the
edge of a platform, roof, bridge, or the like",
+ "answer": "parapet",
+ "enumeration": 7
+ },
+ {
+ "number": 2,
+ "clue": "Playing on the surface; touching lightly",
+ "answer": "lambent",
+ "enumeration": 7
+ },
+ {
+ "number": 3,
+ "clue": "A lighted coal, smoldering amid ashes",
+ "answer": "ember",
+ "enumeration": 5
+ },
+ {
+ "number": 4,
+ "clue": "Any product of human workmanship",
+ "answer": "artifact",
+ "enumeration": 8
+ },
+ {
+ "number": 5,
+ "clue": "Destitute of sap; not juicy",
+ "answer": "sapless",
+ "enumeration": 7
+ },
+ {
+ "number": 6,
+ "clue": "Inspection or analysis of the urine as a means of diagnosis",
+ "answer": "uroscopy",
+ "enumeration": 8
+ },
+ {
+ "number": 7,
+ "clue": "To obtain again",
+ "answer": "reobtain",
+ "enumeration": 8
+ },
+ {
+ "number": 8,
+ "clue": "To cut with a chisel",
+ "answer": "enchisel",
+ "enumeration": 8
+ },
+ {
+ "number": 9,
+ "clue": "Act of beginning; commencement; inception",
+ "answer": "inchoation",
+ "enumeration": 10
+ },
+ {
+ "number": 10,
+ "clue": "A name given in affectionate familiarity",
+ "answer": "nickname",
+ "enumeration": 8
+ },
+ {
+ "number": 11,
+ "clue": "A large extent or tract of land",
+ "answer": "territory",
+ "enumeration": 9
+ },
+ {
+ "number": 12,
+ "clue": "(- of Troy) The beautiful daughter of Zeus and Leda, who was
abducted by Paris",
+ "answer": "helen",
+ "enumeration": 5
+ },
+ {
+ "number": 13,
+ "clue": "One of the fundamental subatomic particles, of negative
charge",
+ "answer": "electron",
+ "enumeration": 8
+ },
+ {
+ "number": 14,
+ "clue": "The Hebrew prophet, who was cast overboard as one who
endangered the ship",
+ "answer": "jonah",
+ "enumeration": 5
+ },
+ {
+ "number": 15,
+ "clue": "An instrument attached to a vehicle which measures the
distance traversed",
+ "answer": "odometer",
+ "enumeration": 8
+ },
+ {
+ "number": 16,
+ "clue": "To ornament with a jewel or with jewels",
+ "answer": "bejewel",
+ "enumeration": 7
+ },
+ {
+ "number": 17,
+ "clue": "One of the three geologic eras of the Phanerozoic Eon",
+ "answer": "paleozoic",
+ "enumeration": 9
+ },
+ {
+ "number": 18,
+ "clue": "Not courteous or gracious",
+ "answer": "uncordial",
+ "enumeration": 9
+ },
+ {
+ "number": 19,
+ "clue": "Landlocked country in Central Asia",
+ "answer": "tajikistan",
+ "enumeration": 10
+ },
+ {
+ "number": 20,
+ "clue": "An asiatic leguminous herb, which seeds are used in preparing
soy",
+ "answer": "soja",
+ "enumeration": 4
+ }
+ ]
+ },
+ "answer": "Pleasure in the job puts perfection in the work.",
+ "zones": [
+ {
+ "rect": [
+ 0,
+ 0,
+ 0,
+ 19
+ ],
+ "style": {
+ "border": 0
+ }
+ },
+ {
+ "rect": [
+ 1,
+ 0,
+ 1,
+ 19
+ ],
+ "style": {
+ "highlight": true
+ }
+ },
+ {
+ "rect": [
+ 5,
+ 0,
+ 5,
+ 19
+ ],
+ "style": {
+ "highlight": true
+ }
+ }
+ ],
+ "volatile": {
+ "com.hiddenquote:arrows": "",
+ "com.hiddenquote:subject": "",
+ "com.hiddenquote:syllables": ""
+ },
+ "com.hiddenquote:arrows": "1,5",
+ "com.hiddenquote:subject": "general",
+ "com.hiddenquote:syllables": [
+ "a",
+ "a",
+ "ar",
+ "be",
+ "bent",
+ "ber",
+ "chis",
+ "cho",
+ "co",
+ "cor",
+ "dial",
+ "dom",
+ "e",
+ "e",
+ "el",
+ "el",
+ "em",
+ "en",
+ "en",
+ "fact",
+ "hel",
+ "i",
+ "ic",
+ "in",
+ "ja",
+ "jew",
+ "jik",
+ "jo",
+ "lam",
+ "le",
+ "lec",
+ "less",
+ "nah",
+ "name",
+ "nick",
+ "o",
+ "o",
+ "ob",
+ "pa",
+ "par",
+ "pet",
+ "py",
+ "re",
+ "ri",
+ "ros",
+ "ry",
+ "sap",
+ "so",
+ "stan",
+ "ta",
+ "tain",
+ "ter",
+ "ter",
+ "ti",
+ "tion",
+ "to",
+ "tron",
+ "u",
+ "un",
+ "zo"
+ ]
+}
- [elpa] externals/hiddenquote 2643793 02/14: Mark Usage as subheading, (continued)
- [elpa] externals/hiddenquote 2643793 02/14: Mark Usage as subheading, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote 2f0c9a8 14/14: ; Mention the origin of this kind of puzzles, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote 5de4c8e 12/14: Tell the hidden-quote source that we want the JSON content, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote e0e3bbd 10/14: ; Fix typo in README.md, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote 5322eb5 08/14: ; Fix README.md, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote f19e074 05/14: hiddenquote.el: Fix commentary section, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote 9366a25 06/14: Add Makefile and READMEs, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote d6d478b 03/14: Add license, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote d4ded2c 11/14: ; hiddenquote.el: Update URL, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote 3785446 04/14: Sort syllables with collation order in mind, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote 9dac184 01/14: Initial commit,
Stefan Monnier <=
- [elpa] externals/hiddenquote 8d3b999 07/14: ; Fix README.md, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote 305f08c 13/14: ; Change Copyright for ELPA inclusion, Stefan Monnier, 2021/02/09
- [elpa] externals/hiddenquote a7ed377 09/14: ; More README.md fixes, Stefan Monnier, 2021/02/09