;;; chess-input.el --- Keyboard entry of algebraic notation, using shortcut notation -*- lexical-binding: t; -*- ;; Copyright (C) 2002-2020 Free Software Foundation, Inc. ;; Author: John Wiegley ;; Maintainer: Mario Lang ;; Keywords: games ;; This 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, or (at your option) any later ;; version. ;; ;; This 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 GNU Emacs. If not, see . ;;; Commentary: ;; This scheme was adapted from the way SCID (), ;; by Shane Hudson, behaves. It is based on standard algebraic notation. ;; You do not need to type all characters from the corresponding SAN of a move, ;; chess-input will automatically pick the move once it is unambiguous. ;; ;; Additionally, optional characters from SAN are treated as such. ;; You do not need to type x or =, although you can, if you prefer to. ;; For instance, "bxc8=N#" can be selected by typing `b c 8 n'. ;;; Code: (require 'chess-algebraic) (require 'chess-ply) (require 'chess-pos) (defvar chess-input-move-string "") (defvar chess-input-moves-pos nil) (defvar chess-input-moves nil) (defvar chess-input-position-function nil) (defvar chess-input-move-function nil) (make-variable-buffer-local 'chess-input-move-string) (make-variable-buffer-local 'chess-input-moves-pos) (make-variable-buffer-local 'chess-input-moves) (make-variable-buffer-local 'chess-input-position-function) (make-variable-buffer-local 'chess-input-move-function) (defgroup chess-input nil "Move input related otpions." :group 'chess) (defcustom chess-input-notation-type :san "Define the notation type to use for move input." :type '(choice (const :tag "Standard (short) algebraic notation" :san) (const :tag "Numeric notation" :numeric))) (defun chess-input-test-move (ply) "Return the given PLY if it matches the user's current input." (let* ((move (chess-ply-to-algebraic ply chess-input-notation-type)) (i 0) (x 0) (l (length move)) (xl (length chess-input-move-string))) (unless (or (and (equal (downcase chess-input-move-string) "ok") (chess-ply-keyword ply :castle)) (and (equal (downcase chess-input-move-string) "oq") (chess-ply-keyword ply :long-castle))) (while (and (< i l) (< x xl)) (let ((move-char (aref move i)) (entry-char (aref chess-input-move-string x))) (cond ((or (and (= move-char ?x) (/= entry-char ?x)) (and (= move-char ?=) (/= entry-char ?=))) (setq i (1+ i))) ((/= entry-char (if (< entry-char ?a) move-char (downcase move-char))) (setq ply nil i l)) (t (setq i (1+ i) x (1+ x))))))) ply)) (defvar chess-display-highlight-legal nil) (declare-function chess-display-redraw "chess-display" (&optional display)) (declare-function chess-display-highlight "chess-display" (display &rest args)) (defun chess-input-display-moves (&optional move-list) (unless move-list (setq move-list (delq nil (mapcar #'chess-input-test-move (cdr chess-input-moves))))) (when chess-display-highlight-legal (chess-display-redraw nil)) (when (> (length chess-input-move-string) 0) (when chess-display-highlight-legal (apply #'chess-display-highlight nil (delete-dups (mapcar #'chess-ply-target move-list)))) (message "[%s] %s" chess-input-move-string (mapconcat (lambda (ply) (chess-ply-to-algebraic ply chess-input-notation-type)) move-list " ")))) (defun chess-input-shortcut-delete () (interactive) (when (and chess-input-move-string (stringp chess-input-move-string) (> (length chess-input-move-string) 0)) (setq chess-input-move-string (substring chess-input-move-string 0 (1- (length chess-input-move-string)))) (chess-input-display-moves))) (defun chess-input-shortcut (&optional display-only) (interactive) (let* ((position (funcall chess-input-position-function)) (color (chess-pos-side-to-move position)) char) (unless (memq last-command '(chess-input-shortcut chess-input-shortcut-delete)) (setq chess-input-move-string nil)) (unless display-only (setq chess-input-move-string (concat chess-input-move-string (char-to-string last-command-event)))) (unless (and chess-input-moves (eq position chess-input-moves-pos) (or (> (length chess-input-move-string) 1) (eq (car chess-input-moves) last-command-event))) (setq char (if (eq (downcase last-command-event) ?o) ?k last-command-event)) (if (or (memq (upcase char) '(?K ?Q ?N ?B ?R ?P)) (and (>= char ?a) (<= char ?h)) (and (>= char ?1) (<= char ?8))) (setq chess-input-moves-pos position chess-input-moves (cons char (sort (cond ((eq char ?b) (nconc (chess-legal-plies position :piece (if color ?P ?p) :file 1) (chess-legal-plies position :piece (if color ?B ?b)))) ((and (>= char ?a) (<= char ?h)) (chess-legal-plies position :piece (if color ?P ?p) :file (chess-file-from-char char))) ((and (>= char ?1) (<= char ?8)) (chess-legal-plies position :color color :file (- char ?1))) (t (chess-legal-plies position :piece (if color (upcase char) (downcase char))))) (lambda (left right) (string-lessp (chess-ply-to-algebraic left) (chess-ply-to-algebraic right))))))))) (let ((moves (delq nil (mapcar #'chess-input-test-move (cdr chess-input-moves))))) (cond ((or (= (length moves) 1) ;; if there is an exact match except for case, it must be an ;; abiguity between a bishop and a b-pawn move. In this ;; case, always take the b-pawn move; to select the bishop ;; move, use B to begin the keyboard shortcut (and (= (length moves) 2) (string= (downcase (chess-ply-to-algebraic (car moves))) (downcase (chess-ply-to-algebraic (cadr moves)))) (setq moves (cdr moves)))) (funcall chess-input-move-function nil (car moves)) (when chess-display-highlight-legal (chess-display-redraw nil)) (setq chess-input-move-string nil chess-input-moves nil chess-input-moves-pos nil)) ((null moves) (chess-input-shortcut-delete)) (t (chess-input-display-moves moves))))) (provide 'chess-input) ;;; chess-input.el ends here