1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;; Chessboard display style using graphical images
8 ;; In addition to what all displays offer, the images display adds a
11 ;; ^ increase the size of the display (if larger pieces exist)
12 ;; V decrease the size of the display (if smaller pieces exist)
13 ;; D use pieces from another directory
15 ;; When using pieces from another directory, they will be loaded and
16 ;; displayed immediately, allowing you to easily browse among
17 ;; different piece sets if you have them (such as the ZIICS set, see
18 ;; the xboard man page).
20 (require 'chess-display)
22 (defgroup chess-images nil
23 "Module for drawing a chess-display using graphical images."
24 :group 'chess-display)
26 (defvar chess-images-cache nil)
27 (defvar chess-images-size nil)
28 (defvar chess-images-sizes nil)
30 (make-variable-buffer-local 'chess-images-cache)
31 (make-variable-buffer-local 'chess-images-size)
32 (make-variable-buffer-local 'chess-images-sizes)
34 (defun chess-images-clear-image-cache (sym value)
36 (setq chess-images-cache nil))
38 (defcustom chess-images-separate-frame (display-multi-frame-p)
39 "If non-nil, display the chessboard in its own frame."
43 (defcustom chess-images-directory
44 (if (file-directory-p "/usr/share/games/xboard/pixmaps")
45 "/usr/share/games/xboard/pixmaps"
46 (expand-file-name "pieces/xboard"
48 (or load-file-name buffer-file-name))))
49 "Directory containing the chess piece bitmap images.
50 You are free to use your own chess pieces, of any size. By default, I
51 assume you have xboard installed, or can go get the pixmaps that come
52 with xboard. I am not an artist, and have not taken the time to draw
55 If you want to draw your own images, each piece must be named
56 COLOR-PIECE.EXT, where COLOR is either black or white, and PIECE is
57 one of rook, knight, bishop, queen, king or pawn.
59 At the moment only XPM has been tested, and I'm afraid it's probably
60 the only one that will work. ImageMagick can be used to convert other
61 graphics formats into XPM for you.
63 Each piece must define the background color to use the symbolic color
64 name \"background\", so that the chess program can use the same pieces
65 for all squares. If you want really custom pieces, you can use the
66 symbolic colors dark_square, light_square and dark_piece and
69 :set 'chess-images-clear-image-cache
72 (defcustom chess-images-default-size nil
73 "The default pixel width to use for chess pieces.
74 If this width is not available, then next smallest will be chosen.
75 If there is none smaller, then the best size available will be chosen.
76 If `chess-images-default-size' is nil (the default), then the best
77 width for the current display is calculated used."
78 :type '(choice integer (const :tag "Best fit" nil))
81 (defcustom chess-images-background-image "blank"
82 "The name of the file used for background squares.
83 This file is optional. If there is no file available by this name, a
84 solid color square will be created and used. This option exists so
85 that specialized squares may be used such as marble tiles, etc."
87 :set 'chess-images-clear-image-cache
90 (defcustom chess-images-border-color (cdr (assq 'background-color
92 "Color to use for the border around pieces."
94 :set 'chess-images-clear-image-cache
97 (defcustom chess-images-dark-color
98 (if (display-color-p) "#77a26d" "gray60")
99 "Color to use for \"dark\" background squares."
101 :set 'chess-images-clear-image-cache
102 :group 'chess-images)
104 (defcustom chess-images-light-color
105 (if (display-color-p) "#c8c365" "gray80")
106 "Color to use for \"light\" background squares."
108 :set 'chess-images-clear-image-cache
109 :group 'chess-images)
111 (defcustom chess-images-black-color
112 (if (display-color-p) "#202020" "gray0")
113 "Color to use for \"black\" pieces."
115 :set 'chess-images-clear-image-cache
116 :group 'chess-images)
118 (defcustom chess-images-white-color
119 (if (display-color-p) "#ffffcc" "gray100")
120 "Color to use for \"white\" pieces."
122 :set 'chess-images-clear-image-cache
123 :group 'chess-images)
125 (defcustom chess-images-highlight-color
126 (if (display-color-p) "#add8e6" "gray90")
127 "Color to use for highlighting pieces that have been selected."
129 :set 'chess-images-clear-image-cache
130 :group 'chess-images)
132 (defcustom chess-images-extension "xpm"
133 "The file extension used for chess display bitmaps."
135 :set 'chess-images-clear-image-cache
136 :group 'chess-images)
138 (defcustom chess-images-border-width 2
139 "This defines the width of the border that surrounds each piece."
140 :type '(choice integer (const :tag "No border" nil))
141 :set 'chess-images-clear-image-cache
142 :group 'chess-images)
144 (defcustom chess-images-popup-function 'chess-images-popup
145 "The function used to popup a chess-images display.
146 The current-buffer is set to the display buffer when this function is
149 :group 'chess-images)
153 (defconst chess-images-piece-names
160 "The names and index values of the different pieces.")
162 (chess-message-catalog 'english
163 '((no-images-fallback . "Could not find any suitable or properly sized chess images")))
165 (defun chess-images-handler (event &rest args)
167 ((eq event 'initialize)
168 (when (display-graphic-p)
169 (chess-images-initialize)
170 (or chess-images-size
172 (chess-message 'no-images-fallback)))))
175 (funcall chess-images-popup-function))
178 (apply 'chess-images-draw args))
180 ((eq event 'draw-square)
181 (apply 'chess-images-draw-square args))
183 ((eq event 'highlight)
184 (apply 'chess-images-highlight args))
186 ((eq event 'start-edit)
187 (setq cursor-type t))
189 ((eq event 'end-edit)
190 (setq cursor-type nil))))
192 (defun chess-images-determine-size ()
193 (let ((display (and (stringp chess-images-separate-frame)
194 chess-images-separate-frame)))
195 (setq cursor-type nil
196 chess-images-cache nil
197 chess-images-size (chess-images-best-size
199 (x-display-pixel-height display)
200 (display-pixel-height))
201 ;; On Macs and Windows, account for
202 ;; the Start/Status bar
203 (if (memq window-system '(mac windows w32))
206 (x-display-pixel-width display)
207 (display-pixel-width)) 20)))))
209 (defun chess-images-initialize ()
210 (let ((map (current-local-map)))
211 (define-key map [?^] 'chess-images-increase-size)
212 (define-key map [?V] 'chess-images-decrease-size)
213 (define-key map [?P] 'chess-images-set-directory))
214 (chess-images-determine-size))
216 (chess-message-catalog 'english
217 '((no-images . "Cannot find any piece images; check `chess-images-directory'")))
219 (defun chess-images-popup ()
220 (unless chess-images-size
221 (chess-error 'no-images))
222 (if chess-images-separate-frame
223 (let* ((size (float (+ (* (or chess-images-border-width 0) 8)
224 (* chess-images-size 8))))
225 (max-char-height (ceiling (/ size (frame-char-height))))
226 (max-char-width (ceiling (/ size (frame-char-width))))
227 (display (and (stringp chess-images-separate-frame)
228 chess-images-separate-frame)))
229 ;; create the frame whenever necessary
230 (chess-display-popup-in-frame (+ max-char-height 2)
232 (cdr (assq 'font (frame-parameters)))))
233 (chess-display-popup-in-window)))
235 (defun chess-images-piece-image (piece rank file)
236 "Return the image used for PIECE at RANK and FILE.
237 Rank and file are important because the colors of the squares on the
238 chess board are light or dark depending on location."
239 (let ((square-color (% (+ file rank) 2))) ; 0 is white
241 (aref chess-images-cache (- 3 square-color))
242 (aref (aref (aref chess-images-cache
243 (if (> piece ?a) 0 1))
244 (if (= square-color 0) 1 0))
245 (nth 2 (assq (downcase piece)
246 chess-images-piece-names))))))
248 (defsubst chess-images-draw-square (pos piece index)
249 "Draw a piece image at point on an already drawn display."
250 (put-text-property pos (1+ pos) 'display
251 (chess-images-piece-image piece (chess-index-rank index)
252 (chess-index-file index))))
254 (defun chess-images-draw (position perspective)
255 "Draw the current chess display position."
256 (let* ((inhibit-redisplay t)
257 (inverted (not perspective))
258 (rank (if inverted 7 0))
259 (file (if inverted 7 0))
260 (pos (point)) new beg)
261 (unless chess-images-cache
262 (chess-images-init-cache)
264 (unless (setq new (= (point-min) (point-max)))
265 (goto-char (point-min)))
266 (while (if inverted (>= rank 0) (< rank 8))
267 (while (if inverted (>= file 0) (< file 8))
268 (let* ((piece (chess-pos-piece position
269 (chess-rf-to-index rank file)))
270 (image (chess-images-piece-image piece rank file)))
273 (put-text-property (point) (1+ (point)) 'display image)
274 (unless (= (1+ (point)) (point-max))
278 (if (= file (if inverted 0 7))
279 (unless (= rank (if inverted 0 7))
281 (insert-image (aref chess-images-cache 5)))
283 beg (point) (list 'intangible (chess-rf-to-index rank file)
284 'rear-nonsticky '(intangible)
285 'chess-coord (chess-rf-to-index rank file)))))
286 (setq file (if inverted (1- file) (1+ file))))
287 (setq file (if inverted 7 0)
288 rank (if inverted (1- rank) (1+ rank))))
289 (set-buffer-modified-p nil)
292 (defun chess-images-highlight (index &optional mode)
293 "Highlight the piece on the board at INDEX, using the given MODE.
295 `selected' show that the piece has been selected for movement.
296 `unselected' show that the piece has been unselected."
297 (let* ((inverted (not (chess-display-perspective nil)))
298 (pos (chess-display-index-pos nil index))
299 (highlight (copy-alist (get-text-property pos 'display))))
300 (setcar (last highlight)
301 (list (cons "light_square" (if (eq mode :selected)
302 chess-images-highlight-color
304 (cons "dark_square" (if (eq mode :selected)
305 chess-images-highlight-color
307 (cons "background" (if (eq mode :selected)
308 chess-images-highlight-color
310 (put-text-property pos (1+ pos) 'display highlight)))
312 (chess-message-catalog 'english
313 '((redrawing-frame . "Redrawing chess display with different size...")
314 (redrawing-frame-done . "Redrawing chess display with different size...done")))
316 (defun chess-images-change-size (size)
317 (let* ((buffer (current-buffer))
318 (window (get-buffer-window buffer))
319 (frame (and window (window-frame window))))
320 (setq chess-images-size size
321 chess-images-cache nil)
323 (delete-frame frame t))
324 (chess-message 'redrawing-frame)
325 (chess-display-update buffer t)
326 (chess-display-popup buffer)
327 (chess-message 'redrawing-frame-done)))
329 (defun chess-images-resize ()
330 "Resize the chessboard based on the frame or window's new size."
331 (chess-images-determine-size)
332 (if chess-images-size
333 (chess-images-change-size chess-images-size)
334 (chess-message 'no-images-fallback)))
336 (defun chess-images-alter-size (test)
337 (let ((sizes chess-images-sizes))
339 (setq sizes (reverse sizes)))
341 (if (funcall test (car sizes) chess-images-size)
343 (chess-images-change-size (car sizes))
345 (setq sizes (cdr sizes))))))
347 (defun chess-images-increase-size ()
348 "Increase the size of the pieces on the board."
350 (chess-images-alter-size '>))
352 (defun chess-images-decrease-size ()
353 "Increase the size of the pieces on the board."
355 (chess-images-alter-size '<))
357 (defun chess-images-sizes ()
358 "Return the set of available sizes for the current piece set.
359 They are returned in ascending order, or nil for no sizes available."
360 (let ((file (expand-file-name (format "black-rook.%s"
361 chess-images-extension)
362 chess-images-directory)))
363 (if (file-readable-p file)
365 (insert-file-contents-literally file)
366 (re-search-forward "\"\\([0-9]+\\)")
367 (setq chess-images-sizes (list (string-to-number (match-string 1)))))
369 (dolist (file (directory-files chess-images-directory nil
370 (format "rdd[0-9]+\\.%s"
371 chess-images-extension)))
372 (if (string-match "rdd\\([0-9]+\\)\\." file)
373 (push (string-to-number (match-string 1 file)) sizes)))
374 (setq chess-images-sizes (sort sizes '<))))))
376 (defun chess-images-best-size (&optional height width)
377 "Return the piece size that works best for a window of HEIGHT."
378 (let* ((size (or chess-images-default-size
379 (min (- (/ (or height (frame-pixel-height)) 8)
380 (or chess-images-border-width 0))
381 (- (/ (or width (frame-pixel-width)) 8)
382 (or chess-images-border-width 0)))))
383 (sizes (chess-images-sizes))
386 (if (> (car sizes) size)
388 (setq last (car sizes)
390 (or last (and chess-images-default-size
391 (let (chess-images-default-size)
392 (chess-images-best-size height width))))))
394 (defun chess-images-set-directory (directory)
395 "Increase the size of the pieces on the board."
396 (interactive "DUse chess pieces in: ")
397 (setq chess-images-directory directory
398 chess-images-sizes (chess-images-sizes)
399 chess-images-size (chess-images-best-size)
400 chess-images-cache nil)
401 (chess-images-alter-size '=))
403 (defun chess-images-create-xpm (height &optional width)
405 (insert "/* XPM */\n")
406 (insert "static char *chessdotel[] = {\n")
407 (insert "/* columns rows colors chars-per-pixel */\n")
408 (insert (format "\"%d %d 2 1\",\n" (or width height) height))
409 (insert "\" c red s void\",\n")
410 (insert "\". c red s background\",\n")
411 (insert "/* pixels */\n")
413 (insert ?\" (make-string (or width height) ?.) ?\" ?, ?\n))
418 (defun chess-images-hack-xpm (file add-height color)
419 "Hack an XPM to append ADD-HEIGHT rows of COLOR.
420 This is necessary for bizzare Emacs reasons."
422 (if (string-match "\\`/\\* XPM \\*/" file)
424 (insert-file-contents-literally file))
425 (goto-char (point-min))
426 (if (re-search-forward (concat "\"\\([0-9]+\\)\\s-+\\([0-9]+\\)\\s-+"
427 "\\([0-9]+\\)\\s-+\\([0-9]+\\)\"") nil t)
428 (let* ((width (string-to-number (match-string 1)))
429 (height (string-to-number (match-string 2)))
430 (colors (string-to-number (match-string 3)))
431 (chars-per-color (string-to-number (match-string 4)))
432 (color-char (make-string chars-per-color ?~)))
433 (replace-match (int-to-string (+ height add-height)) t t nil 2)
437 (if (re-search-forward
438 (format "^\"\\(..\\)\\s-*c\\s-+%s" color) nil t)
440 (substring (match-string 1) 0 chars-per-color)))))
441 (replace-match (int-to-string (1+ colors)) t t nil 3)
443 (insert "\n\"" color-char "\tc " color "\","))
445 (forward-line (1+ colors))
446 (while (looking-at "/\\*")
448 (dotimes (i add-height)
455 (defsubst chess-images-create-image (file background &optional foreground)
456 "Create an Emacs image object, for insertion on the board."
457 (let ((syms (list (nth background
458 `(("dark_square" . ,chess-images-dark-color)
459 ("light_square" . ,chess-images-light-color)))
461 `(("background" . ,chess-images-dark-color)
462 ("background" . ,chess-images-light-color))))))
465 (list (nth foreground
466 `(("dark_piece" . ,chess-images-black-color)
467 ("light_piece" . ,chess-images-white-color))))))
468 (if chess-images-border-width
470 (chess-images-hack-xpm file chess-images-border-width
471 chess-images-border-color)
472 nil t :color-symbols syms)
473 (create-image file nil (string-match "\\`/\\* XPM \\*/" file)
474 :color-symbols syms))))
476 (chess-message-catalog 'english
477 '((piece-images-loading . "Loading chess piece images...")
478 (piece-images-loaded . "Loading chess piece images...done")))
480 (defun chess-images-init-cache ()
481 "Initialize the display image cache."
482 (chess-message 'piece-images-loading)
484 ;; Make a vector of two vectors of 6-item vectors: each piece of
485 ;; each color on each color square; and lastly two slots for the
487 (setq chess-images-cache
488 (vector (vector (make-vector 6 nil)
490 (vector (make-vector 6 nil)
494 (let* ((colors '("black" "white"))
495 (backgrounds (list chess-images-dark-color
496 chess-images-light-color))
497 (piece-colors (list chess-images-black-color
498 chess-images-white-color))
499 blank name image-data)
502 (dolist (piece chess-images-piece-names)
503 (let ((file (expand-file-name
504 (format "%s-%s.%s" (nth c colors) (nth 1 piece)
505 chess-images-extension)
506 chess-images-directory)))
507 (if (file-readable-p file)
508 (aset (aref (aref chess-images-cache c) b)
510 (chess-images-create-image file b c))
511 ;; try loading an xboard format file
512 (setq file (expand-file-name
513 (format "%c%c%c%d.%s" (car piece)
515 (if (= b 0) ?d ?l) chess-images-size
516 chess-images-extension)
517 chess-images-directory))
518 (aset (aref (aref chess-images-cache c) b)
520 (chess-images-create-image file b c)))))))
523 (setq blank (expand-file-name
524 (format "%s.%s" chess-images-background-image
525 chess-images-extension)
526 chess-images-directory)))
527 (aset chess-images-cache 2 (chess-images-create-image blank 0))
528 (aset chess-images-cache 3 (chess-images-create-image blank 1)))
529 ;; try loading an xboard format file
531 (setq name (format "dsq%d.%s" chess-images-size
532 chess-images-extension)
533 blank (expand-file-name name chess-images-directory)))
534 (aset chess-images-cache 2 (chess-images-create-image blank 0))
536 (setq blank (expand-file-name name chess-images-directory))
537 (aset chess-images-cache 3 (chess-images-create-image blank 1)))
538 ;; if all else fails, create one
542 ((string= chess-images-extension "xpm")
543 (chess-images-create-xpm chess-images-size))))
544 (aset chess-images-cache 2 (chess-images-create-image image-data 0))
545 (aset chess-images-cache 3 (chess-images-create-image image-data 1))))
547 (when chess-images-border-width
548 (aset chess-images-cache 4
550 (chess-images-create-xpm
551 chess-images-border-width
552 (+ (* 8 chess-images-size)
553 (* 9 chess-images-border-width)))
555 (list (cons "background" chess-images-border-color))))
556 (aset chess-images-cache 5
558 (chess-images-create-xpm chess-images-size
559 chess-images-border-width)
561 (list (cons "background" chess-images-border-color))))))
563 ;; let the garbage collector know we're through here
565 (chess-message 'piece-images-loaded))
567 (provide 'chess-images)
569 ;;; chess-images.el ends here