1297 lines
45 KiB
EmacsLisp
1297 lines
45 KiB
EmacsLisp
|
;;; n-back.el --- n-back game
|
|||
|
;;
|
|||
|
;; Author: Lennart Borgman (lennart O borgman A gmail O com)
|
|||
|
;; Created: 2009-05-23 Sat
|
|||
|
(defconst n-back:version "0.5");; Version:
|
|||
|
;; Last-Updated: 2009-08-04 Tue
|
|||
|
;; URL:
|
|||
|
;; Keywords:
|
|||
|
;; Compatibility:
|
|||
|
;;
|
|||
|
;; Features that might be required by this library:
|
|||
|
;;
|
|||
|
;; `winsize'.
|
|||
|
;;
|
|||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|||
|
;;
|
|||
|
;;; Commentary:
|
|||
|
;;
|
|||
|
;; n-back game for brain training. See `n-back-game' for more
|
|||
|
;; information.
|
|||
|
;;
|
|||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|||
|
;;
|
|||
|
;;; Change log:
|
|||
|
;;
|
|||
|
;;
|
|||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|||
|
;;
|
|||
|
;; This program is free software; you can redistribute it and/or
|
|||
|
;; modify it under the terms of the GNU General Public License as
|
|||
|
;; published by the Free Software Foundation; either version 3, or
|
|||
|
;; (at your option) any later version.
|
|||
|
;;
|
|||
|
;; This program is distributed in the hope that it will be useful,
|
|||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|||
|
;; General Public License for more details.
|
|||
|
;;
|
|||
|
;; You should have received a copy of the GNU General Public License
|
|||
|
;; along with this program; see the file COPYING. If not, write to
|
|||
|
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
|
|||
|
;; Floor, Boston, MA 02110-1301, USA.
|
|||
|
;;
|
|||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|||
|
;;
|
|||
|
;;; Code:
|
|||
|
|
|||
|
;;(eval-when-compile (require 'viper))
|
|||
|
|
|||
|
;; (setq n-back-trials 2)
|
|||
|
(eval-when-compile (require 'cl))
|
|||
|
(eval-when-compile (require 'nxhtml-base nil t))
|
|||
|
(eval-when-compile (require 'nxhtml-web-vcs nil t))
|
|||
|
(require 'winsize nil t) ;; Ehum...
|
|||
|
|
|||
|
(defvar n-back-game-window nil)
|
|||
|
(defvar n-back-game-buffer nil)
|
|||
|
|
|||
|
(defvar n-back-ctrl-window nil)
|
|||
|
(defvar n-back-ctrl-buffer nil)
|
|||
|
|
|||
|
(defvar n-back-info-window nil)
|
|||
|
(defvar n-back-info-buffer nil)
|
|||
|
|
|||
|
(defvar n-back-trials-left nil)
|
|||
|
(defvar n-back-timer nil)
|
|||
|
(defvar n-back-clear-timer nil)
|
|||
|
|
|||
|
(defvar n-back-result nil)
|
|||
|
(defvar n-back-this-result nil)
|
|||
|
|
|||
|
(defvar n-back-ring nil)
|
|||
|
|
|||
|
(defvar n-back-num-active nil)
|
|||
|
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defgroup n-back nil
|
|||
|
"Customizations for `n-back-game' game."
|
|||
|
:group 'games)
|
|||
|
|
|||
|
(defgroup n-back-feel nil
|
|||
|
"Customizations for `n-back-game' game keys, faces etc."
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
(defface n-back-ok
|
|||
|
'((t (:foreground "black" :background "green")))
|
|||
|
"Face for OK answer."
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defface n-back-bad
|
|||
|
'((t (:foreground "black" :background "OrangeRed1")))
|
|||
|
"Face for bad answer."
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defface n-back-hint
|
|||
|
'((t (:foreground "black" :background "gold")))
|
|||
|
"Face for bad answer."
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defface n-back-do-now
|
|||
|
'((((background dark)) (:foreground "yellow"))
|
|||
|
(t (:foreground "blue")))
|
|||
|
"Face for start and stop hints."
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defface n-back-game-word
|
|||
|
'((t (:foreground "black")))
|
|||
|
"Face for word displayed in game."
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defface n-back-header
|
|||
|
'((((background dark)) (:background "OrangeRed4"))
|
|||
|
(t (:background "gold")))
|
|||
|
"Face for headers."
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defface n-back-keybinding
|
|||
|
'((((background dark)) (:background "purple4"))
|
|||
|
(t (:background "OliveDrab1")))
|
|||
|
"Face for key bindings."
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defface n-back-last-result
|
|||
|
'((((background dark)) (:background "OliveDrab4"))
|
|||
|
(t (:background "yellow")))
|
|||
|
"Face for last game result header."
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defface n-back-welcome
|
|||
|
'((((background dark)) (:foreground "OliveDrab3"))
|
|||
|
(t (:foreground "OliveDrab4")))
|
|||
|
"Face for welcome string"
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defface n-back-welcome-header
|
|||
|
'((t (:height 2.0)))
|
|||
|
"Face for welcome header."
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defcustom n-back-level 1
|
|||
|
"The n-Back level."
|
|||
|
:type '(radio (const 1)
|
|||
|
(const 2)
|
|||
|
(const 3)
|
|||
|
(const 4))
|
|||
|
:set (lambda (sym val)
|
|||
|
(set-default sym val)
|
|||
|
(when (featurep 'n-back)
|
|||
|
(n-back-update-control-buffer)
|
|||
|
(n-back-update-info)))
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
(defcustom n-back-active-match-types '(position color sound)
|
|||
|
"Active match types."
|
|||
|
:type '(set (const position)
|
|||
|
(const color)
|
|||
|
(const sound)
|
|||
|
(const word))
|
|||
|
:set (lambda (sym val)
|
|||
|
(set-default sym val)
|
|||
|
(setq n-back-num-active (length val))
|
|||
|
(when (featurep 'n-back)
|
|||
|
(n-back-init-control-status)
|
|||
|
(n-back-update-control-buffer)
|
|||
|
(n-back-update-info)))
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
(defcustom n-back-allowed-match-types '(position color sound word)
|
|||
|
"Match types allowed in auto challenging."
|
|||
|
:type '(set (const position)
|
|||
|
(const color)
|
|||
|
(const sound)
|
|||
|
(const word))
|
|||
|
:set (lambda (sym val)
|
|||
|
(set-default sym val)
|
|||
|
(when (featurep 'n-back)
|
|||
|
(n-back-set-random-match-types (length n-back-active-match-types) nil)
|
|||
|
(n-back-init-control-status)
|
|||
|
(n-back-update-control-buffer)
|
|||
|
(n-back-update-info)))
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
(defcustom n-back-auto-challenge t
|
|||
|
"Automatic challenge decrease/increase."
|
|||
|
:type 'boolean
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
(defun n-back-toggle-auto-challenge ()
|
|||
|
"Toggle `n-back-auto-challenge'."
|
|||
|
(interactive)
|
|||
|
(let ((val (not n-back-auto-challenge)))
|
|||
|
(customize-set-variable 'n-back-auto-challenge val)
|
|||
|
(customize-set-value 'n-back-auto-challenge val)))
|
|||
|
|
|||
|
(defcustom n-back-colors
|
|||
|
'("gold" "orange red" "lawn green" "peru" "pink" "gray" "light blue")
|
|||
|
"Random colors to display."
|
|||
|
:type '(repeat color)
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
(defcustom n-back-words "you cat going me forest crying brown"
|
|||
|
"Random words to display."
|
|||
|
:type 'string
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
(defcustom n-back-sound-volume 0.2
|
|||
|
"Sound volume 0-1."
|
|||
|
:type 'float
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defcustom n-back-sounds '("c:/program files/brain workshop/res" "piano-")
|
|||
|
"Random sounds location."
|
|||
|
:type '(list (directory :tag "Directory")
|
|||
|
(regexp :tag "File name regexp"))
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
(defcustom n-back-keys
|
|||
|
'(
|
|||
|
[?p]
|
|||
|
[?c]
|
|||
|
[?s]
|
|||
|
[?w]
|
|||
|
)
|
|||
|
"Key bindings for answering."
|
|||
|
:type '(list
|
|||
|
(key-sequence :tag "position key")
|
|||
|
(key-sequence :tag "color key")
|
|||
|
(key-sequence :tag "sound key")
|
|||
|
(key-sequence :tag "word key")
|
|||
|
)
|
|||
|
;; :set (lambda (sym val)
|
|||
|
;; (set-default sym val)
|
|||
|
;; (n-back-make-keymap))
|
|||
|
:group 'n-back-feel)
|
|||
|
|
|||
|
(defvar n-back-control-mode-map nil)
|
|||
|
|
|||
|
(defun n-back-key-binding (what)
|
|||
|
"Return key binding used for WHAT match answers."
|
|||
|
(nth
|
|||
|
(case what
|
|||
|
(position 0)
|
|||
|
(color 1)
|
|||
|
(sound 2)
|
|||
|
(word 3))
|
|||
|
n-back-keys))
|
|||
|
|
|||
|
(defun n-back-make-keymap ()
|
|||
|
"Make keymap for the game."
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(define-key map [?1] 'n-back-change-level)
|
|||
|
(define-key map [?2] 'n-back-change-level)
|
|||
|
(define-key map [?3] 'n-back-change-level)
|
|||
|
(define-key map [?4] 'n-back-change-level)
|
|||
|
(define-key map [?5] 'n-back-change-level)
|
|||
|
(define-key map [?6] 'n-back-change-level)
|
|||
|
(define-key map [??] 'n-back-help)
|
|||
|
(define-key map [?\ ] 'n-back-play)
|
|||
|
(define-key map [(control ?g)] 'n-back-stop)
|
|||
|
(define-key map [?-] 'n-back-decrease-speed)
|
|||
|
(define-key map [?+] 'n-back-increase-speed)
|
|||
|
|
|||
|
(define-key map [(control ?r)] 'n-back-reset-game-to-saved)
|
|||
|
(define-key map [(control ?s)] 'n-back-save-game-settings)
|
|||
|
|
|||
|
(define-key map [?t ?p] 'n-back-toggle-position)
|
|||
|
(define-key map [?t ?c] 'n-back-toggle-color)
|
|||
|
(define-key map [?t ?s] 'n-back-toggle-sound)
|
|||
|
(define-key map [?t ?w] 'n-back-toggle-word)
|
|||
|
|
|||
|
(define-key map [?T ?a] 'n-back-toggle-auto-challenge)
|
|||
|
(define-key map [up] 'n-back-challenge-up)
|
|||
|
(define-key map [down] 'n-back-challenge-down)
|
|||
|
|
|||
|
(define-key map [?T ?p] 'n-back-toggle-allowed-position)
|
|||
|
(define-key map [?T ?c] 'n-back-toggle-allowed-color)
|
|||
|
(define-key map [?T ?s] 'n-back-toggle-allowed-sound)
|
|||
|
(define-key map [?T ?w] 'n-back-toggle-allowed-word)
|
|||
|
|
|||
|
(define-key map (n-back-key-binding 'position) 'n-back-position-answer)
|
|||
|
(define-key map (n-back-key-binding 'color) 'n-back-color-answer)
|
|||
|
(define-key map (n-back-key-binding 'sound) 'n-back-sound-answer)
|
|||
|
(define-key map (n-back-key-binding 'word) 'n-back-word-answer)
|
|||
|
;;(define-key map [t] 'ignore)
|
|||
|
(setq n-back-control-mode-map map)))
|
|||
|
|
|||
|
(defvar n-back-display-hint nil)
|
|||
|
(defcustom n-back-hint t
|
|||
|
"Display hints - learning mode."
|
|||
|
:type 'boolean
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
(defvar n-back-sound-files nil)
|
|||
|
;;(n-back-get-sound-files)
|
|||
|
(defun n-back-get-sound-files ()
|
|||
|
"Get sound file names."
|
|||
|
(let ((dir (nth 0 n-back-sounds))
|
|||
|
(regexp (nth 1 n-back-sounds)))
|
|||
|
(when (file-directory-p dir)
|
|||
|
(setq n-back-sound-files (directory-files dir nil regexp)))))
|
|||
|
|
|||
|
(defun n-back-toggle-position ()
|
|||
|
"Toggle use of position in `n-back-active-match-types'."
|
|||
|
(interactive)
|
|||
|
(n-back-toggle 'position))
|
|||
|
|
|||
|
(defun n-back-toggle-color ()
|
|||
|
"Toggle use of color in `n-back-active-match-types'."
|
|||
|
(interactive)
|
|||
|
(n-back-toggle 'color))
|
|||
|
|
|||
|
(defun n-back-toggle-sound ()
|
|||
|
"Toggle use of sound in `n-back-active-match-types'."
|
|||
|
(interactive)
|
|||
|
(n-back-toggle 'sound))
|
|||
|
|
|||
|
(defun n-back-toggle-word ()
|
|||
|
"Toggle use of word in `n-back-active-match-types'."
|
|||
|
(interactive)
|
|||
|
(n-back-toggle 'word))
|
|||
|
|
|||
|
(defun n-back-toggle (match-type)
|
|||
|
"Toggle use of MATCH-TYPE in `n-back-active-match-types'."
|
|||
|
(n-back-toggle-1 match-type 'n-back-active-match-types))
|
|||
|
|
|||
|
(defun n-back-toggle-allowed-position ()
|
|||
|
"Toggle use of position in `n-back-allowed-match-types'."
|
|||
|
(interactive)
|
|||
|
(n-back-toggle-allowed 'position))
|
|||
|
|
|||
|
(defun n-back-toggle-allowed-color ()
|
|||
|
"Toggle use of color in `n-back-allowed-match-types'."
|
|||
|
(interactive)
|
|||
|
(n-back-toggle-allowed 'color))
|
|||
|
|
|||
|
(defun n-back-toggle-allowed-sound ()
|
|||
|
"Toggle use of sound in `n-back-allowed-match-types'."
|
|||
|
(interactive)
|
|||
|
(n-back-toggle-allowed 'sound))
|
|||
|
|
|||
|
(defun n-back-toggle-allowed-word ()
|
|||
|
"Toggle use of word in `n-back-allowed-match-types'."
|
|||
|
(interactive)
|
|||
|
(n-back-toggle-allowed 'word))
|
|||
|
|
|||
|
(defun n-back-toggle-allowed (match-type)
|
|||
|
"Toggle use of MATCH-TYPE in `n-back-allowed-match-types'."
|
|||
|
(n-back-toggle-1 match-type 'n-back-allowed-match-types))
|
|||
|
|
|||
|
(defun n-back-sort-types (types)
|
|||
|
"Sort TYPES to order used in defcustoms here."
|
|||
|
(sort types
|
|||
|
(lambda (a b)
|
|||
|
(let ((all '(position color sound word)))
|
|||
|
(< (length (memq a all))
|
|||
|
(length (memq b all)))))))
|
|||
|
|
|||
|
(defun n-back-toggle-1 (match-type active-list-sym)
|
|||
|
"Toggle use of MATCH-TYPE in list ACTIVE-LIST-SYM."
|
|||
|
(let (active-types)
|
|||
|
(if (memq match-type (symbol-value active-list-sym))
|
|||
|
(setq active-types (delq match-type (symbol-value active-list-sym)))
|
|||
|
(setq active-types (cons match-type (symbol-value active-list-sym))))
|
|||
|
(setq active-types (n-back-sort-types active-types))
|
|||
|
(customize-set-variable active-list-sym active-types)
|
|||
|
(customize-set-value active-list-sym active-types)))
|
|||
|
|
|||
|
(defcustom n-back-sec-per-trial 3.0
|
|||
|
"Seconds per trial."
|
|||
|
:type 'float
|
|||
|
:set (lambda (sym val)
|
|||
|
(set-default sym val)
|
|||
|
(when (featurep 'n-back)
|
|||
|
(n-back-update-info)))
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
(defun n-back-decrease-speed ()
|
|||
|
"Decrease speed of trials."
|
|||
|
(interactive)
|
|||
|
(setq n-back-sec-per-trial (+ n-back-sec-per-trial 0.25))
|
|||
|
(when (> n-back-sec-per-trial 5.0)
|
|||
|
(setq n-back-sec-per-trial 5.0))
|
|||
|
(n-back-update-info))
|
|||
|
|
|||
|
(defun n-back-increase-speed ()
|
|||
|
"Increase speed of trials."
|
|||
|
(interactive)
|
|||
|
(let ((sec (- n-back-sec-per-trial 0.25)))
|
|||
|
(when (< sec 1.0)
|
|||
|
(setq sec 1.0))
|
|||
|
(customize-set-variable 'n-back-sec-per-trial sec)
|
|||
|
(customize-set-value 'n-back-sec-per-trial sec)))
|
|||
|
|
|||
|
(defun n-back-help ()
|
|||
|
"Show help for `n-back-game' game."
|
|||
|
(interactive)
|
|||
|
(save-selected-window
|
|||
|
(describe-function 'n-back-game)))
|
|||
|
|
|||
|
(defun n-back-change-level (level)
|
|||
|
"Change n-Back level to LEVEL."
|
|||
|
(interactive (progn
|
|||
|
(if (and (numberp last-input-event)
|
|||
|
(>= last-input-event ?1)
|
|||
|
(<= last-input-event ?9))
|
|||
|
(list (- last-input-event ?0))
|
|||
|
(list (string-to-number (read-string "n Back: "))))))
|
|||
|
(customize-set-variable 'n-back-level level)
|
|||
|
(customize-set-value 'n-back-level level))
|
|||
|
|
|||
|
(defvar n-back-frame nil)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun n-back-game ()
|
|||
|
"Emacs n-Back game.
|
|||
|
This game is supposed to increase your working memory and fluid
|
|||
|
intelligence.
|
|||
|
|
|||
|
In this game something is shown for half a second on the screen
|
|||
|
and maybe a sound is played. You should then answer if parts of
|
|||
|
it is the same as you have seen or heard before. This is
|
|||
|
repeated for about 20 trials.
|
|||
|
|
|||
|
You answer with the keys shown in the bottom window.
|
|||
|
|
|||
|
In the easiest version of the game you should answer if you have
|
|||
|
just seen or heard what is shown now. By default the game gets
|
|||
|
harder as you play it with success. Then first the number of
|
|||
|
items presented in a trial grows. After that it gets harder by
|
|||
|
that you have to somehow remember not the last item, but the item
|
|||
|
before that \(or even earlier). That is what \"n-Back\" stands
|
|||
|
for.
|
|||
|
|
|||
|
Note that remember does not really mean remember clearly. The
|
|||
|
game is for training your brain getting used to keep those things
|
|||
|
in the working memory, maybe as a cross-modal unit. You are
|
|||
|
supposed to just nearly be able to do what you do in the game.
|
|||
|
And you are supposed to have fun, that is what your brain like.
|
|||
|
|
|||
|
You should probably not overdue this. Half an hour a day playing
|
|||
|
might be an optimal time according to some people.
|
|||
|
|
|||
|
The game is shamelessly modeled after Brain Workshop, see URL
|
|||
|
`http://brainworkshop.sourceforge.net/' just for the fun of
|
|||
|
getting it into Emacs. The game resembles but it not the same as
|
|||
|
that used in the report by Jaeggi mentioned at the above URL.
|
|||
|
|
|||
|
Not all features in Brain Workshop are implemented here, but some
|
|||
|
new are maybe ... - and you have it available here in Emacs."
|
|||
|
;; -----
|
|||
|
;; Below is a short excerpt from the report by Jaeggi et al which
|
|||
|
;; gave the idea to the game:
|
|||
|
|
|||
|
;; Training task. For the training task, we used the same material
|
|||
|
;; as described by Jaeggi et al. (33), which was a dual n-Back task
|
|||
|
;; where squares at eight different locations were presented
|
|||
|
;; sequentially on a computer screen at a rate of 3 s (stimulus
|
|||
|
;; length, 500 ms; interstimulus interval, 2,500 ms).
|
|||
|
;; Simultaneously with the presentation of the squares, one of eight
|
|||
|
;; consonants was presented sequentially through headphones. A
|
|||
|
;; response was required whenever one of the presented stimuli
|
|||
|
;; matched the one presented n positions back in the sequence. The
|
|||
|
;; value of n was the same for both streams of stimuli. There were
|
|||
|
;; six auditory and six visual targets per block (four appearing in
|
|||
|
;; only one modality, and two appearing in both modalities
|
|||
|
;; simultaneously), and their positions were determined randomly.
|
|||
|
;; Participants made responses manually by pressing on the letter
|
|||
|
;; ‘‘A’’ of a standard keyboard with their left index finger for
|
|||
|
;; visual targets, and on the letter ‘‘L’’ with their right index
|
|||
|
;; finger for auditory targets. No responses were required for
|
|||
|
;; non-targets.
|
|||
|
(interactive)
|
|||
|
(n-back-make-keymap)
|
|||
|
(when window-system
|
|||
|
(unless (frame-live-p n-back-frame)
|
|||
|
(setq n-back-frame (make-frame
|
|||
|
(list '(name . "n-back game")
|
|||
|
'(tool-bar-lines . 0)
|
|||
|
'(menu-bar-lines . 0)
|
|||
|
(case (frame-parameter nil 'background-mode)
|
|||
|
(light '(background-color . "cornsilk"))
|
|||
|
(dark '(background-color . "MidnightBlue"))
|
|||
|
(otherwise nil))
|
|||
|
'(height . 45)
|
|||
|
'(width . 150)))))
|
|||
|
(select-frame n-back-frame)
|
|||
|
(raise-frame n-back-frame))
|
|||
|
(n-back-cancel-timers)
|
|||
|
(n-back-get-sound-files)
|
|||
|
(unless n-back-sound-files
|
|||
|
(when (memq 'sound n-back-allowed-match-types)
|
|||
|
(n-back-toggle-allowed-sound))
|
|||
|
(when (memq 'sound n-back-active-match-types)
|
|||
|
(n-back-toggle-sound)))
|
|||
|
(n-back-init-control-status)
|
|||
|
(n-back-setup-windows)
|
|||
|
)
|
|||
|
|
|||
|
(defconst n-back-match-types
|
|||
|
'((position ": position match" nil)
|
|||
|
(color ": color match" nil)
|
|||
|
(sound ": sound match" nil)
|
|||
|
(word ": word match" nil)
|
|||
|
))
|
|||
|
|
|||
|
(defvar n-back-control-status nil
|
|||
|
"For showing status in control window.")
|
|||
|
(setq n-back-control-status nil)
|
|||
|
|
|||
|
;;(n-back-set-match-status 'position 'bad)
|
|||
|
(defun n-back-set-match-status (match-type status)
|
|||
|
"Set MATCH-TYPE status to STATUS for control window."
|
|||
|
(unless (memq status '(ok bad miss nil)) (error "n-back: Bad status=%s" status))
|
|||
|
(let ((entry (assoc match-type n-back-control-status)))
|
|||
|
(setcar (cddr entry) status)
|
|||
|
))
|
|||
|
|
|||
|
;;(n-back-clear-match-status)
|
|||
|
(defun n-back-clear-match-status ()
|
|||
|
"Clear match status for control window."
|
|||
|
;;(dolist (entry n-back-control-status)
|
|||
|
(dolist (entry n-back-match-types)
|
|||
|
(setcar (cddr entry) nil)
|
|||
|
))
|
|||
|
|
|||
|
;; (n-back-init-control-status)
|
|||
|
(defun n-back-init-control-status ()
|
|||
|
"Init match status for control window."
|
|||
|
(setq n-back-control-status nil)
|
|||
|
(dolist (what n-back-active-match-types)
|
|||
|
(setq n-back-control-status
|
|||
|
(cons (assoc what n-back-match-types)
|
|||
|
n-back-control-status))))
|
|||
|
|
|||
|
(defsubst n-back-is-playing ()
|
|||
|
"Return non-nil when game is active."
|
|||
|
(timerp n-back-timer))
|
|||
|
|
|||
|
;;(n-back-update-control-buffer)
|
|||
|
(defun n-back-update-control-buffer ()
|
|||
|
"Update content of control buffer."
|
|||
|
(save-match-data ;; runs in timer
|
|||
|
(when (buffer-live-p n-back-ctrl-buffer)
|
|||
|
(with-current-buffer n-back-ctrl-buffer
|
|||
|
(setq buffer-read-only nil)
|
|||
|
(erase-buffer)
|
|||
|
(insert (propertize (format "%s %s-back"
|
|||
|
(let ((n (length n-back-active-match-types)))
|
|||
|
(cond
|
|||
|
((= 1 n) "Single")
|
|||
|
((= 2 n) "Dual")
|
|||
|
((= 3 n) "Triple")
|
|||
|
))
|
|||
|
n-back-level
|
|||
|
) 'face 'n-back-header)
|
|||
|
(propertize
|
|||
|
(if (n-back-is-playing) " Press C-g to stop" " Press SPACE to play")
|
|||
|
'face 'n-back-do-now)
|
|||
|
(if (n-back-is-playing) (format " Left %s" n-back-trials-left) "")
|
|||
|
"\n")
|
|||
|
;;(unless n-back-control-status (n-back-init-control-status))
|
|||
|
(dolist (entry n-back-control-status)
|
|||
|
(let* ((what (nth 0 entry))
|
|||
|
(msg (nth 1 entry))
|
|||
|
(sts (nth 2 entry))
|
|||
|
(key (key-description (n-back-key-binding what))))
|
|||
|
;;(setq msg (concat (key-description (n-back-key-binding what)) msg))
|
|||
|
(cond
|
|||
|
((eq sts 'bad)
|
|||
|
(setq msg (propertize (concat key msg) 'face 'n-back-bad)))
|
|||
|
((eq sts 'ok)
|
|||
|
(setq msg (propertize (concat key msg) 'face 'n-back-ok)))
|
|||
|
((eq sts 'miss)
|
|||
|
(setq msg (concat
|
|||
|
(if n-back-display-hint
|
|||
|
(propertize key 'face 'n-back-header)
|
|||
|
key)
|
|||
|
msg)))
|
|||
|
((not sts)
|
|||
|
(setq msg (concat key msg)))
|
|||
|
(t
|
|||
|
(error "n-back:Unknown sts=%s" sts)
|
|||
|
))
|
|||
|
(insert msg " "))
|
|||
|
)
|
|||
|
(when n-back-display-hint
|
|||
|
(setq n-back-display-hint nil)
|
|||
|
(run-with-timer 0.1 nil 'n-back-update-control-buffer))
|
|||
|
(setq buffer-read-only t)
|
|||
|
(if (window-live-p n-back-ctrl-window)
|
|||
|
(with-selected-window n-back-ctrl-window
|
|||
|
(goto-char 1))
|
|||
|
(goto-char 1))))))
|
|||
|
|
|||
|
(defcustom n-back-trials 20
|
|||
|
"Number of trials per session."
|
|||
|
:type 'integer
|
|||
|
:group 'n-back)
|
|||
|
|
|||
|
;;(n-back-compute-result-values n-back-result)
|
|||
|
(defvar n-back-result-values nil)
|
|||
|
(defun n-back-compute-single-result-value (entry)
|
|||
|
"Compute result stored in ENTRY."
|
|||
|
(let* ((what (nth 0 entry))
|
|||
|
(good (nth 1 entry))
|
|||
|
(bad (nth 2 entry))
|
|||
|
(miss (nth 3 entry))
|
|||
|
(err (+ bad miss))
|
|||
|
;;(tot (+ good bad miss 0.0))
|
|||
|
;;(gnum 6)
|
|||
|
;;(weighted-err (* err (/ gnum tot)))
|
|||
|
)
|
|||
|
(cons what (if (= 0 good)
|
|||
|
0
|
|||
|
(/ (- n-back-trials err 0.0)
|
|||
|
n-back-trials)))))
|
|||
|
|
|||
|
(defun n-back-compute-result-values (result)
|
|||
|
"Compute result values from game result RESULT."
|
|||
|
(let ((results nil))
|
|||
|
(dolist (entry result)
|
|||
|
(let ((res (n-back-compute-single-result-value entry)))
|
|||
|
(setq results (cons res results))))
|
|||
|
(setq n-back-result-values (reverse results))))
|
|||
|
|
|||
|
;; Thresholds
|
|||
|
(defun n-back-view-threshold-discussion-page ()
|
|||
|
"View some discussion of threshold."
|
|||
|
(interactive)
|
|||
|
(browse-url "http://groups.google.com/group/brain-training/browse_thread/thread/f4bfa452943c2a2d/ba31adfd0b97771c?lnk=gst&q=threshold#ba31adfd0b97771c"))
|
|||
|
|
|||
|
;;(n-back-set-next-challenge)
|
|||
|
(defvar n-back-worst nil)
|
|||
|
|
|||
|
(defvar n-back-challenge-change nil)
|
|||
|
|
|||
|
(defun n-back-set-next-challenge ()
|
|||
|
"Set next game difficulty level from last game result."
|
|||
|
(let ((r 2.8)) ;; stay as default
|
|||
|
(setq n-back-worst nil)
|
|||
|
(dolist (res n-back-result-values)
|
|||
|
(when (< (cdr res) r)
|
|||
|
(setq r (cdr res))
|
|||
|
(setq n-back-worst res)))
|
|||
|
(setq n-back-challenge-change (if (< r 0.74)
|
|||
|
'down
|
|||
|
(if (> r 0.91)
|
|||
|
'up
|
|||
|
'stay)))
|
|||
|
(n-back-change-challenge n-back-challenge-change)))
|
|||
|
|
|||
|
(defun n-back-challenge-up ()
|
|||
|
"Make the game harder."
|
|||
|
(interactive)
|
|||
|
(n-back-change-challenge 'up))
|
|||
|
|
|||
|
(defun n-back-challenge-down ()
|
|||
|
"Make the game easier."
|
|||
|
(interactive)
|
|||
|
(n-back-change-challenge 'down))
|
|||
|
|
|||
|
(defun n-back-change-challenge (challenge-change)
|
|||
|
"Change game difficulty level by CHALLENGE-CHANGE."
|
|||
|
(let ((new-level n-back-level)
|
|||
|
(new-num-active n-back-num-active)
|
|||
|
(num-allowed (length n-back-allowed-match-types)))
|
|||
|
(case challenge-change
|
|||
|
(down
|
|||
|
(if (= 1 n-back-num-active)
|
|||
|
(unless (= 1 n-back-level)
|
|||
|
(setq new-num-active (min 3 num-allowed))
|
|||
|
(setq new-level (1- n-back-level)))
|
|||
|
(setq new-num-active (1- n-back-num-active))))
|
|||
|
(up
|
|||
|
(if (or (<= 3 n-back-num-active)
|
|||
|
(<= num-allowed n-back-num-active))
|
|||
|
(progn
|
|||
|
(setq new-level (1+ n-back-level))
|
|||
|
(setq new-num-active 1))
|
|||
|
(setq new-num-active (min 3 (1+ n-back-num-active))))))
|
|||
|
;;(when (= new-level 0) (setq new-level 1))
|
|||
|
;;(when (= new-num-active 0) (setq new-num-active 1))
|
|||
|
(when (and (= new-level n-back-level)
|
|||
|
(= new-num-active n-back-num-active))
|
|||
|
(setq n-back-challenge-change 'stay))
|
|||
|
(unless (= new-level n-back-level)
|
|||
|
(customize-set-variable 'n-back-level new-level)
|
|||
|
(customize-set-value 'n-back-level new-level))
|
|||
|
(n-back-set-random-match-types new-num-active (car n-back-worst))))
|
|||
|
|
|||
|
(defun n-back-set-random-match-types (num worst)
|
|||
|
"Select NUM random match types.
|
|||
|
If type WORST is non-nil try to include that."
|
|||
|
(let ((alen (length n-back-allowed-match-types))
|
|||
|
(old-types n-back-active-match-types)
|
|||
|
types)
|
|||
|
(unless (<= num alen)
|
|||
|
(error "n-back: Too many match types required = %s" num))
|
|||
|
(when (and worst
|
|||
|
(< 1 num)
|
|||
|
(memq worst n-back-allowed-match-types))
|
|||
|
(add-to-list 'types worst))
|
|||
|
(while (< (length types) num)
|
|||
|
(add-to-list 'types (nth (random alen) n-back-allowed-match-types)))
|
|||
|
(setq types (n-back-sort-types types))
|
|||
|
(unless (equal old-types types)
|
|||
|
(customize-set-variable 'n-back-active-match-types types)
|
|||
|
(customize-set-value 'n-back-active-match-types types))))
|
|||
|
|
|||
|
;; (defcustom n-back-keybinding-color "OliveDrab1"
|
|||
|
;; "Background color for key binding hints."
|
|||
|
;; :type 'color
|
|||
|
;; :group 'n-back)
|
|||
|
|
|||
|
(defun n-back-update-info ()
|
|||
|
"Update info buffer."
|
|||
|
(when (buffer-live-p n-back-info-buffer)
|
|||
|
(when (window-live-p n-back-info-window)
|
|||
|
(set-window-buffer n-back-info-window n-back-info-buffer))
|
|||
|
(with-current-buffer n-back-info-buffer
|
|||
|
(setq buffer-read-only nil)
|
|||
|
(erase-buffer)
|
|||
|
|
|||
|
(insert (propertize "n-back" 'face 'n-back-header)
|
|||
|
" "
|
|||
|
(propertize "Help: ?" 'face 'n-back-keybinding))
|
|||
|
|
|||
|
;; Auto challenging
|
|||
|
(insert "\n\nAuto challenging: "
|
|||
|
(if n-back-auto-challenge "on " "off ")
|
|||
|
(propertize "toggle: Ta" 'face 'n-back-keybinding))
|
|||
|
|
|||
|
(insert "\n Manually change challenging: "
|
|||
|
(propertize "up-arrow/down-arrow" 'face 'n-back-keybinding))
|
|||
|
|
|||
|
(insert "\n Allowed match types: ")
|
|||
|
(dolist (type n-back-allowed-match-types)
|
|||
|
(insert (format "%s " type)))
|
|||
|
(insert (propertize "toggle: T" 'face 'n-back-keybinding))
|
|||
|
|
|||
|
;; Current game
|
|||
|
(insert "\n\nCurrent game:")
|
|||
|
|
|||
|
(insert (format "\n n Back: %s " n-back-level)
|
|||
|
(propertize "change: number 1-9" 'face 'n-back-keybinding))
|
|||
|
(insert "\n Match types: ")
|
|||
|
(dolist (type n-back-active-match-types)
|
|||
|
(insert (format "%s " type)))
|
|||
|
(insert (propertize "toggle: t" 'face 'n-back-keybinding))
|
|||
|
|
|||
|
(insert (format "\n %.2f seconds per trial " n-back-sec-per-trial)
|
|||
|
(propertize "change: +/-" 'face 'n-back-keybinding))
|
|||
|
|
|||
|
;; Save and restore
|
|||
|
(insert "\n\n")
|
|||
|
(insert "Game settings: "
|
|||
|
(propertize "reset: C-r" 'face 'n-back-keybinding)
|
|||
|
" "
|
|||
|
(propertize "save: C-s" 'face 'n-back-keybinding))
|
|||
|
|
|||
|
(insert "\n\n")
|
|||
|
(unless (or (n-back-is-playing)
|
|||
|
(not n-back-result))
|
|||
|
(insert (propertize (format "Last result, %s" n-back-challenge-change)
|
|||
|
'face 'n-back-last-result)
|
|||
|
"\n Good-Bad-Miss:")
|
|||
|
(dolist (entry n-back-result)
|
|||
|
(let* ((what (nth 0 entry))
|
|||
|
(good (nth 1 entry))
|
|||
|
(bad (nth 2 entry))
|
|||
|
(miss (nth 3 entry))
|
|||
|
(tot (+ good bad miss 0.0))
|
|||
|
(res (n-back-compute-single-result-value entry)))
|
|||
|
(insert (format " %s: %s-%s-%s (%d%%)"
|
|||
|
(key-description (n-back-key-binding what))
|
|||
|
good
|
|||
|
bad
|
|||
|
miss
|
|||
|
(floor (* 100 (cdr res))))))))
|
|||
|
|
|||
|
(setq buffer-read-only t))))
|
|||
|
|
|||
|
(defun n-back-show-welcome (msg)
|
|||
|
"Show welcome startup info and message MSG."
|
|||
|
(when (and n-back-game-buffer
|
|||
|
(buffer-live-p n-back-game-buffer))
|
|||
|
(with-current-buffer n-back-game-buffer
|
|||
|
(let ((src (or (when (boundp 'nxhtml-install-dir)
|
|||
|
(expand-file-name "nxhtml/doc/img/fun-brain-2.png" nxhtml-install-dir))
|
|||
|
"c:/program files/brain workshop/res/brain_graphic.png"))
|
|||
|
img
|
|||
|
buffer-read-only)
|
|||
|
(erase-buffer)
|
|||
|
;;(insert (propertize "\nEmacs n-back game (after Brain Workshop)\n\n" 'face '(:height 2.0)))
|
|||
|
(insert (propertize "\nEmacs n-back game (after Brain Workshop)\n\n" 'face 'n-back-welcome-header))
|
|||
|
(unless (file-exists-p src)
|
|||
|
(n-back-maybe-download-files (file-name-directory src) (list (file-name-nondirectory src))))
|
|||
|
(if (file-exists-p src)
|
|||
|
(condition-case err
|
|||
|
(setq img (create-image src nil nil
|
|||
|
:relief 0
|
|||
|
;;:margin inlimg-margins
|
|||
|
))
|
|||
|
(error (setq img (error-message-string err))))
|
|||
|
(setq img (concat "Image not found: " src)))
|
|||
|
(if (stringp img)
|
|||
|
(insert img)
|
|||
|
(insert-image img))
|
|||
|
(insert (propertize "\n\nPlay for fun and maybe a somewhat happier brain"
|
|||
|
'face 'n-back-welcome))
|
|||
|
(when msg (insert "\n\n" msg))
|
|||
|
))))
|
|||
|
|
|||
|
(defun n-back-setup-windows ()
|
|||
|
"Setup game frame and windows."
|
|||
|
(delete-other-windows)
|
|||
|
;; Info
|
|||
|
(split-window-horizontally)
|
|||
|
(setq n-back-info-window (next-window (frame-first-window)))
|
|||
|
(setq n-back-info-buffer (get-buffer-create "* n-back info *"))
|
|||
|
(when (< 75 (window-width n-back-info-window))
|
|||
|
(with-selected-window n-back-info-window
|
|||
|
(enlarge-window (- 75 (window-width n-back-info-window)) t)))
|
|||
|
(with-current-buffer n-back-info-buffer
|
|||
|
(n-back-control-mode)
|
|||
|
(setq wrap-prefix " "))
|
|||
|
(n-back-update-info)
|
|||
|
;; Control
|
|||
|
(split-window-vertically)
|
|||
|
(setq n-back-ctrl-window (next-window (frame-first-window)))
|
|||
|
(setq n-back-ctrl-buffer (get-buffer-create "* n-back control *"))
|
|||
|
(set-window-buffer n-back-ctrl-window n-back-ctrl-buffer)
|
|||
|
(with-current-buffer n-back-ctrl-buffer (n-back-control-mode))
|
|||
|
(n-back-update-control-buffer)
|
|||
|
(fit-window-to-buffer n-back-ctrl-window)
|
|||
|
(set-window-dedicated-p n-back-ctrl-window t)
|
|||
|
;; Game
|
|||
|
(setq n-back-game-window (frame-first-window))
|
|||
|
(setq n-back-game-buffer (get-buffer-create "*n-back game*"))
|
|||
|
(set-window-buffer n-back-game-window n-back-game-buffer)
|
|||
|
(set-window-dedicated-p n-back-game-window t)
|
|||
|
(with-current-buffer n-back-game-buffer (n-back-control-mode))
|
|||
|
(n-back-show-welcome nil)
|
|||
|
;; Position in control window
|
|||
|
(select-window n-back-ctrl-window)
|
|||
|
)
|
|||
|
|
|||
|
;;(n-back-display "str" 1 0 3 3 6)
|
|||
|
(defun n-back-display (str x y cols rows max-strlen color)
|
|||
|
"Display a trial.
|
|||
|
Display item with text STR at column X in row Y using COLS
|
|||
|
columns and ROWS rows. Strings to display have max length
|
|||
|
MAX-STRLEN. Display item with background color COLOR."
|
|||
|
(unless (< x cols) (error "n-back: Not x=%s < cols=%s" x cols))
|
|||
|
(unless (< y rows) (error "Not y=%s < rows=%s" y rows))
|
|||
|
(unless str (setq str ""))
|
|||
|
(with-current-buffer n-back-game-buffer
|
|||
|
(let* (buffer-read-only
|
|||
|
(tot-str "")
|
|||
|
;; Pad spaces left, two right, four between
|
|||
|
(game-w (window-width n-back-game-window))
|
|||
|
(pad-x 0)
|
|||
|
(scale (if (not window-system)
|
|||
|
1.0
|
|||
|
(/ (* 1.0 game-w)
|
|||
|
(+ (* 2 pad-x)
|
|||
|
(* (1- cols) 4)
|
|||
|
(* cols max-strlen)))))
|
|||
|
(str-diff (- max-strlen (length str)))
|
|||
|
(str-l-len (/ str-diff 2))
|
|||
|
(str-r-len (- max-strlen (length str) str-l-len))
|
|||
|
(face-spec (if window-system
|
|||
|
(list :inherit 'n-back-game-word :background color :height scale)
|
|||
|
(list :inherit 'n-back-game-word :background color)))
|
|||
|
(str-disp (propertize
|
|||
|
(concat (make-string str-l-len 32) str (make-string str-r-len 32))
|
|||
|
'face face-spec))
|
|||
|
(col-str (concat
|
|||
|
(make-string pad-x ?p)
|
|||
|
(make-string
|
|||
|
(+ (* x (+ 4 max-strlen)))
|
|||
|
32
|
|||
|
;;?x
|
|||
|
)))
|
|||
|
;; Pad lines above and below, two between
|
|||
|
(pad-y 0)
|
|||
|
(game-h (window-body-height n-back-game-window))
|
|||
|
(game-h-scaled (/ game-h scale))
|
|||
|
(lines-between (/ (- game-h-scaled rows (* 2 pad-y))
|
|||
|
(1- rows)))
|
|||
|
(row-scaled (+ pad-y (* y (1+ lines-between)) (1- y)))
|
|||
|
(row-num (if (= y 0)
|
|||
|
pad-y
|
|||
|
(round row-scaled)))
|
|||
|
(row-str (make-string row-num ?\n)))
|
|||
|
(setq show-trailing-whitespace nil)
|
|||
|
;;(setq cursor-type nil)
|
|||
|
(erase-buffer)
|
|||
|
(setq tot-str row-str)
|
|||
|
(setq tot-str (concat tot-str col-str))
|
|||
|
(insert (propertize tot-str 'face (list :height scale)))
|
|||
|
(insert str-disp)
|
|||
|
)))
|
|||
|
|
|||
|
;; (setq timer-list nil)
|
|||
|
;;(n-back-display-in-timer)
|
|||
|
;; (setq n-back-trials-left 3)
|
|||
|
|
|||
|
(defun n-back-clear-game-window ()
|
|||
|
"Erase game buffer."
|
|||
|
(save-match-data ;; runs in timer
|
|||
|
(with-current-buffer n-back-game-buffer
|
|||
|
(let (buffer-read-only)
|
|||
|
(erase-buffer)))))
|
|||
|
|
|||
|
(defun n-back-play ()
|
|||
|
"Start playing."
|
|||
|
(interactive)
|
|||
|
(message " ") ;; For easier reading *Messages*
|
|||
|
(n-back-update-info)
|
|||
|
(if (not n-back-active-match-types)
|
|||
|
(message (propertize "No active match types"
|
|||
|
'face 'secondary-selection))
|
|||
|
;;(setq n-back-result nil)
|
|||
|
(n-back-init-control-status)
|
|||
|
(n-back-init-this-result)
|
|||
|
(n-back-cancel-timers)
|
|||
|
(winsize-set-mode-line-colors t)
|
|||
|
(setq n-back-ring (make-ring (1+ n-back-level)))
|
|||
|
(n-back-clear-game-window)
|
|||
|
(setq n-back-trials-left (+ n-back-trials n-back-level))
|
|||
|
(random t)
|
|||
|
(n-back-start-main-timer)
|
|||
|
(n-back-update-control-buffer)))
|
|||
|
|
|||
|
(defun n-back-start-main-timer ()
|
|||
|
"Start main game timer."
|
|||
|
(setq n-back-timer
|
|||
|
(run-with-timer
|
|||
|
n-back-sec-per-trial
|
|||
|
nil ;;n-back-sec-per-trial
|
|||
|
'n-back-display-in-timer)))
|
|||
|
|
|||
|
(defun n-back-maybe-download-files (dir file-name-list)
|
|||
|
(nxhtml-get-missing-files (file-relative-name dir nxhtml-install-dir) file-name-list))
|
|||
|
|
|||
|
(defun n-back-finish-game ()
|
|||
|
"Finish the game."
|
|||
|
(n-back-cancel-timers)
|
|||
|
(fit-window-to-buffer n-back-ctrl-window)
|
|||
|
(setq n-back-result n-back-this-result)
|
|||
|
(n-back-compute-result-values n-back-result)
|
|||
|
(when n-back-auto-challenge (n-back-set-next-challenge))
|
|||
|
(n-back-update-info)
|
|||
|
(n-back-init-control-status)
|
|||
|
(n-back-clear-match-status)
|
|||
|
(n-back-update-control-buffer)
|
|||
|
(n-back-show-welcome "Game over")
|
|||
|
(with-current-buffer n-back-game-buffer
|
|||
|
;;(setq n-back-challenge-change 'up)
|
|||
|
(let (buffer-read-only)
|
|||
|
(insert
|
|||
|
"\n\n"
|
|||
|
(case n-back-challenge-change
|
|||
|
(up "Congratulations! I see you need more challenge, raising difficulty!")
|
|||
|
(down "Making it a bit easier for now to make your playing more fun.")
|
|||
|
(otherwise "This game challenges seems the right way for you now.")))
|
|||
|
(let* ((dir (when (boundp 'nxhtml-install-dir)
|
|||
|
(expand-file-name "nxhtml/doc/img/" nxhtml-install-dir)))
|
|||
|
(up-imgs '("rembrandt-self-portrait.jpg"
|
|||
|
"bacchante2.jpg"
|
|||
|
"giraffe.jpg"
|
|||
|
"Las_Medulas.jpg"
|
|||
|
))
|
|||
|
(t-imgs '("continue-play.jpg"
|
|||
|
"Toco_toucan.jpg"
|
|||
|
"raindrops2.jpg"
|
|||
|
"divine2.jpg"
|
|||
|
;;"butterflies.png"
|
|||
|
"volga.jpg"
|
|||
|
"healthy_feet2.jpg"
|
|||
|
))
|
|||
|
;; (setq n-back-trials 1)
|
|||
|
(pic (when dir (case n-back-challenge-change
|
|||
|
(up (nth (random (length up-imgs)) up-imgs))
|
|||
|
(otherwise (nth (random (length t-imgs)) t-imgs)))))
|
|||
|
(src (when dir (expand-file-name pic dir)))
|
|||
|
img)
|
|||
|
(when (and src (not (file-exists-p src)))
|
|||
|
;; Time to download?
|
|||
|
(n-back-maybe-download-files (file-name-directory src) (append up-imgs t-imgs nil)))
|
|||
|
(when (and src (file-exists-p src))
|
|||
|
(condition-case err
|
|||
|
(setq img (create-image src nil nil
|
|||
|
:relief 0
|
|||
|
))
|
|||
|
(error (setq img (error-message-string err)))))
|
|||
|
(if (stringp img)
|
|||
|
img
|
|||
|
(insert "\n\n")
|
|||
|
(insert-image img)))))
|
|||
|
(message "Game over"))
|
|||
|
|
|||
|
(defun n-back-display-random ()
|
|||
|
"Display a random item."
|
|||
|
(when (current-message) (message ""))
|
|||
|
;;(message "here start display")
|
|||
|
(let* ((use-position (memq 'position n-back-active-match-types))
|
|||
|
(use-color (memq 'color n-back-active-match-types))
|
|||
|
(use-sound (memq 'sound n-back-active-match-types))
|
|||
|
(use-word (memq 'word n-back-active-match-types))
|
|||
|
(old-rec (when (n-back-match-possible)
|
|||
|
(ring-ref n-back-ring (1- n-back-level))))
|
|||
|
(cols 3)
|
|||
|
(rows 3)
|
|||
|
(x (if use-position (random 3) 1))
|
|||
|
(y (if use-position (random 3) 1))
|
|||
|
(old-x (if use-position (nth 1 old-rec)))
|
|||
|
(old-y (if use-position (nth 2 old-rec)))
|
|||
|
(color (nth (if use-color (random (length n-back-colors)) 0) n-back-colors))
|
|||
|
(old-color (if use-color (nth 3 old-rec)))
|
|||
|
(sound (when use-sound (expand-file-name (nth (random (length n-back-sound-files))
|
|||
|
n-back-sound-files)
|
|||
|
(nth 0 n-back-sounds))))
|
|||
|
(old-sound (if use-sound (nth 4 old-rec)))
|
|||
|
(words (when use-word (split-string n-back-words)))
|
|||
|
(word (when use-word (nth (random (length words)) words)))
|
|||
|
(old-word (when use-word (nth 5 old-rec)))
|
|||
|
(str (if word word "")) ;(format "%s" n-back-trials-left))
|
|||
|
(max-strlen (if words
|
|||
|
(+ 2 (apply 'max (mapcar (lambda (w) (length w)) words)))
|
|||
|
5))
|
|||
|
(compensate 24)
|
|||
|
)
|
|||
|
;; To get more targets make it more plausible that it is the same here.
|
|||
|
;; (/ (- 6 (/ 20.0 8)) 20)
|
|||
|
(when old-rec
|
|||
|
(when (and use-position
|
|||
|
(not (and (= x old-x)
|
|||
|
(= y old-y)))
|
|||
|
(< (random 100) compensate))
|
|||
|
(setq x (nth 1 old-rec))
|
|||
|
(setq y (nth 2 old-rec)))
|
|||
|
(when (and use-color
|
|||
|
(not (equal color old-color))
|
|||
|
(< (random 100) compensate))
|
|||
|
(setq color (nth 3 old-rec)))
|
|||
|
(when (and use-sound
|
|||
|
(not (equal sound old-sound))
|
|||
|
(< (random 100) compensate))
|
|||
|
(setq sound (nth 4 old-rec)))
|
|||
|
(when (and use-word
|
|||
|
(not (equal word old-word))
|
|||
|
(< (random 100) compensate))
|
|||
|
(setq word (nth 5 old-rec))))
|
|||
|
(setq str word) ;; fix-me
|
|||
|
(ring-insert n-back-ring (list str x y color sound word))
|
|||
|
;;(message "here before display")
|
|||
|
(n-back-display str x y cols rows max-strlen color)
|
|||
|
;;(when sound (play-sound (list 'sound :file sound)))
|
|||
|
;;(message "here before clear-m")
|
|||
|
(n-back-clear-match-status)
|
|||
|
;;(message "here before position")
|
|||
|
(when (and use-position (n-back-matches 'position)) (n-back-set-match-status 'position 'miss))
|
|||
|
;;(message "here before color")
|
|||
|
(when (and use-color (n-back-matches 'color)) (n-back-set-match-status 'color 'miss))
|
|||
|
;;(message "here before sound")
|
|||
|
(when (and use-sound (n-back-matches 'sound)) (n-back-set-match-status 'sound 'miss))
|
|||
|
;;(message "here before word")
|
|||
|
(when (and use-word (n-back-matches 'word)) (n-back-set-match-status 'word 'miss))
|
|||
|
(setq n-back-display-hint n-back-hint)
|
|||
|
;;(message "here before control")
|
|||
|
(n-back-update-control-buffer)
|
|||
|
;;(message "here before clear timer")
|
|||
|
(setq n-back-clear-timer (run-with-timer 0.5 nil 'n-back-clear-game-window))
|
|||
|
;;(message "here before sound timer")
|
|||
|
(when sound (run-with-timer 0.01 nil 'n-back-play-sound-in-timer sound))
|
|||
|
;;(message "here exit display")
|
|||
|
))
|
|||
|
|
|||
|
(defun n-back-display-in-timer ()
|
|||
|
"Display a trial in a timer."
|
|||
|
(condition-case err
|
|||
|
(save-match-data ;; runs in timer
|
|||
|
(n-back-add-result)
|
|||
|
(if (>= 0 (setq n-back-trials-left (1- n-back-trials-left)))
|
|||
|
(n-back-finish-game)
|
|||
|
(n-back-display-random)
|
|||
|
(n-back-start-main-timer)
|
|||
|
;;(message "after start-main-timer")
|
|||
|
))
|
|||
|
(error (message "n-back-display: %s" (error-message-string err))
|
|||
|
(n-back-cancel-timers))))
|
|||
|
|
|||
|
(defun n-back-play-sound-in-timer (sound-file)
|
|||
|
"Play sound SOUND-FILE in a timer."
|
|||
|
(condition-case err
|
|||
|
(save-match-data ;; runs in timer
|
|||
|
(play-sound (list 'sound :file sound-file :volume n-back-sound-volume)))
|
|||
|
(error (message "n-back-sound: %s" (error-message-string err))
|
|||
|
(n-back-cancel-timers))))
|
|||
|
|
|||
|
|
|||
|
;;; Answers
|
|||
|
|
|||
|
;;(defvar n-back-answers nil)
|
|||
|
|
|||
|
(defun n-back-init-this-result ()
|
|||
|
"Init `n-back-this-result'."
|
|||
|
(setq n-back-this-result nil)
|
|||
|
(dolist (sts-entry n-back-control-status)
|
|||
|
(let* ((what (nth 0 sts-entry))
|
|||
|
(res-entry (list what 0 0 0)))
|
|||
|
(setq n-back-this-result (cons res-entry n-back-this-result)))))
|
|||
|
|
|||
|
(defun n-back-match-possible ()
|
|||
|
"Return t if enouch entries have been shown to match."
|
|||
|
(= (ring-length n-back-ring) (1+ n-back-level)))
|
|||
|
|
|||
|
(defun n-back-add-result ()
|
|||
|
"Add result of last trial."
|
|||
|
(when (n-back-match-possible)
|
|||
|
(dolist (sts-entry n-back-control-status)
|
|||
|
(let* ((what (nth 0 sts-entry))
|
|||
|
(sts (nth 2 sts-entry))
|
|||
|
(matches (n-back-matches what))
|
|||
|
(num (cond
|
|||
|
((eq sts 'ok) 1)
|
|||
|
((eq sts 'bad) 2)
|
|||
|
;;((eq sts nil) (when matches 3))
|
|||
|
((eq sts 'miss) 3)
|
|||
|
((not sts) nil)
|
|||
|
(t (error "n-back: Bad status=%s" sts))))
|
|||
|
(res-entry (when num (assoc what n-back-this-result)))
|
|||
|
(lst (when num (nthcdr num res-entry))))
|
|||
|
(when num
|
|||
|
(if res-entry
|
|||
|
(setcar lst (1+ (car lst)))
|
|||
|
(setq res-entry (list what 0 0 0))
|
|||
|
;;(setq lst (nthcdr num res-entry))
|
|||
|
(setq n-back-this-result (cons res-entry n-back-this-result))))))))
|
|||
|
|
|||
|
(defun n-back-matches-position ()
|
|||
|
"Return non-nil iff last trial position match."
|
|||
|
(when (n-back-match-possible)
|
|||
|
(let* ((comp-item (ring-ref n-back-ring n-back-level))
|
|||
|
(curr-item (ring-ref n-back-ring 0))
|
|||
|
(comp-x (nth 1 comp-item))
|
|||
|
(curr-x (nth 1 curr-item))
|
|||
|
(comp-y (nth 2 comp-item))
|
|||
|
(curr-y (nth 2 curr-item)))
|
|||
|
(and (= comp-y curr-y)
|
|||
|
(= comp-x curr-x)))))
|
|||
|
|
|||
|
(defun n-back-matches-color ()
|
|||
|
"Return non-nil iff last trial color match."
|
|||
|
(when (n-back-match-possible)
|
|||
|
(let* ((comp-item (ring-ref n-back-ring n-back-level))
|
|||
|
(curr-item (ring-ref n-back-ring 0))
|
|||
|
(comp-color (nth 3 comp-item))
|
|||
|
(curr-color (nth 3 curr-item)))
|
|||
|
(equal comp-color curr-color))))
|
|||
|
|
|||
|
(defun n-back-matches-sound ()
|
|||
|
"Return non-nil iff last trial sound match."
|
|||
|
(when (n-back-match-possible)
|
|||
|
(let* ((comp-item (ring-ref n-back-ring n-back-level))
|
|||
|
(curr-item (ring-ref n-back-ring 0))
|
|||
|
(comp-sound (nth 4 comp-item))
|
|||
|
(curr-sound (nth 4 curr-item)))
|
|||
|
(equal comp-sound curr-sound))))
|
|||
|
|
|||
|
(defun n-back-matches-word ()
|
|||
|
"Return non-nil iff last trial word match."
|
|||
|
(when (n-back-match-possible)
|
|||
|
(let* ((comp-item (ring-ref n-back-ring n-back-level))
|
|||
|
(curr-item (ring-ref n-back-ring 0))
|
|||
|
(comp-word (nth 5 comp-item))
|
|||
|
(curr-word (nth 5 curr-item)))
|
|||
|
(equal comp-word curr-word))))
|
|||
|
|
|||
|
(defun n-back-matches (what)
|
|||
|
"Return non-nil iff last trial part WHAT match."
|
|||
|
(cond
|
|||
|
((eq what 'position) (n-back-matches-position))
|
|||
|
((eq what 'color) (n-back-matches-color))
|
|||
|
((eq what 'sound) (n-back-matches-sound))
|
|||
|
((eq what 'word) (n-back-matches-word))
|
|||
|
(t (error "n-back: Unknown match type: %s" what))))
|
|||
|
|
|||
|
(defun n-back-answer (what)
|
|||
|
"Tell that you think WHAT matched."
|
|||
|
(when (n-back-is-playing)
|
|||
|
(if (memq what n-back-active-match-types)
|
|||
|
(if (n-back-match-possible)
|
|||
|
(let ((sts (if (n-back-matches what) 'ok 'bad)))
|
|||
|
(n-back-set-match-status what sts)
|
|||
|
(n-back-update-control-buffer))
|
|||
|
(message "%s n-back items must be displayed before anything can match"
|
|||
|
n-back-level))
|
|||
|
(message "%s match is not active" what)
|
|||
|
(ding t))))
|
|||
|
|
|||
|
(defun n-back-position-answer ()
|
|||
|
"Tell that you think position matched."
|
|||
|
(interactive)
|
|||
|
(n-back-answer 'position))
|
|||
|
|
|||
|
(defun n-back-color-answer ()
|
|||
|
"Tell that you think color matched."
|
|||
|
(interactive)
|
|||
|
(n-back-answer 'color))
|
|||
|
|
|||
|
(defun n-back-sound-answer ()
|
|||
|
"Tell that you think sound matched."
|
|||
|
(interactive)
|
|||
|
(n-back-answer 'sound))
|
|||
|
|
|||
|
(defun n-back-word-answer ()
|
|||
|
"Tell that you think word matched."
|
|||
|
(interactive)
|
|||
|
(n-back-answer 'word))
|
|||
|
|
|||
|
(defun n-back-stop ()
|
|||
|
"Stop playing."
|
|||
|
(interactive)
|
|||
|
(n-back-cancel-timers)
|
|||
|
(n-back-update-control-buffer)
|
|||
|
(message "Stopped n-back game")
|
|||
|
(n-back-show-welcome "Stopped"))
|
|||
|
|
|||
|
(defvar viper-emacs-state-mode-list) ;; silence compiler
|
|||
|
(defvar viper-emacs-state-hook) ;; silence compiler
|
|||
|
|
|||
|
(define-derived-mode n-back-control-mode nil "N-back"
|
|||
|
"Mode for controlling n-back game."
|
|||
|
(setq cursor-type nil)
|
|||
|
(setq buffer-read-only t)
|
|||
|
(set (make-local-variable 'viper-emacs-state-mode-list) '(n-back-control-mode))
|
|||
|
(set (make-local-variable 'viper-emacs-state-hook) nil) ;; in vis cursor
|
|||
|
(abbrev-mode -1)
|
|||
|
(setq show-trailing-whitespace nil)
|
|||
|
(when (fboundp 'visual-line-mode) (visual-line-mode 1))
|
|||
|
(n-back-make-keymap))
|
|||
|
|
|||
|
(defun n-back-cancel-timers ()
|
|||
|
"Cancel game timers."
|
|||
|
(when (timerp n-back-timer)
|
|||
|
(cancel-timer n-back-timer))
|
|||
|
(setq n-back-timer nil)
|
|||
|
(when (timerp n-back-clear-timer)
|
|||
|
(cancel-timer n-back-clear-timer))
|
|||
|
(setq n-back-clear-timer nil)
|
|||
|
(winsize-set-mode-line-colors nil))
|
|||
|
|
|||
|
(defvar n-back-game-settings-symbols
|
|||
|
'(
|
|||
|
;;n-back-keys
|
|||
|
n-back-level
|
|||
|
n-back-active-match-types
|
|||
|
n-back-allowed-match-types
|
|||
|
n-back-auto-challenge
|
|||
|
;;n-back-colors
|
|||
|
;;n-back-words
|
|||
|
;;n-back-sound-volume
|
|||
|
;;n-back-sounds
|
|||
|
n-back-sec-per-trial
|
|||
|
;;n-back-keybinding-color
|
|||
|
;;n-back-trials
|
|||
|
))
|
|||
|
|
|||
|
(defun n-back-save-game-settings ()
|
|||
|
"Save game settings."
|
|||
|
(interactive)
|
|||
|
(dolist (var n-back-game-settings-symbols)
|
|||
|
)
|
|||
|
(custom-save-all))
|
|||
|
|
|||
|
(defun n-back-reset-game-to-saved ()
|
|||
|
"Reset game playing options to saved values."
|
|||
|
(interactive)
|
|||
|
(dolist (pass '(1 2))
|
|||
|
(dolist (var n-back-game-settings-symbols)
|
|||
|
(if (= pass 1)
|
|||
|
;; pass 1 is for my lousy programming:
|
|||
|
(condition-case err
|
|||
|
(custom-reevaluate-setting var)
|
|||
|
(error nil))
|
|||
|
(custom-reevaluate-setting var)))))
|
|||
|
|
|||
|
(provide 'n-back)
|
|||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|||
|
;;; n-back.el ends here
|