
После того, как я не смог ответить на звонок в дочкином телефоне, я решил что что-то надо сделать. Специалисты утверждают, что еще не все потеряно и с помощью специальных технологий можно не отстать от подрастающего поколения. Одним из таких средств является N-Back. Так как с сотовым телефоном с точскрином я не справлюсь (замкнутый круг получается), я попытался найти такое приложение под J2ME. Не нашел и решил написать сам. Но вот проблема — Scala и Clojure не поддерживают J2ME, а выучить Java не потренировавшись на еще не написанной программе мне будет тяжело. После некоторого гугуления решение было найдено — Hecl, слегка переработанный Tcl.
Надо сказать, что программировал на Tcl я очень давно — тогда я работал на компьютере SGI O2 и завидовал тем, кто могут играть в «lines» (они же «шарики»). К внешнему виду приложений я не очень притязателен, и с помощью Tcl с библиотекой Tk эту проблему я решил.
Tcl — простой императивный скриптовой язык, синтаксисом напоминающий Unix Shell (и иногда использовавшийся в его качестве). По расширяемости его можно сравнить с Fort и Lisp. Простота встраивания его в приложения на языке C сделало его популярным среди разработчиков САПР.
Hecl унаследовал многие черты Tcl, только интерируется с приложениями на Java, а не на C. Разработу упрощает наличие REPL, в отличие от классического tclsh поддерживающего историю команд и редактирование строки.
На сайте Hecl есть готовый MIDlet с примерами и даже минимальной средой разработки. В jar-архиве есть файл script.hcl — достачно его подменить (не забыв подправить .jad), что бы запустить свой скрипт на J2ME-платформе.
И так, начнем. Нам надо получить случайный символ
set alph {A C G T} set alphsize [llen $alph] proc rand {} { global alph alphsize lindex $alph [* [random] $alphsize] }
Присваивание переменной выполняется командой set. Имя изменяемой переменной пишется просто, как в Python, а при использовании надо добавлять '$', как в Perl. При желании можно написать
и переменной bbb присвоется значение «ccc».set aaa bbb set $aaa ccc
Строки, как и в Unix Shell, обычно не надо заключать в кавычки. Если все таки придется, кроме обычных двойных кавычек можно использовать сбалансированные фигурные скобки ({}) — строки могут быть «вложенными».
Строка разбивается по пробелам (получатся список) и первое слово трактуется как имя процедуры — здесь Tcl немного напоминает Lisp. Вложенные в команду вызовы других команд заключаются в квадратные скобки ([]).
Команды + и * — нововведение Hecl. В оригинальном Tcl приходилось вызывать специальный DSL
почти как в Shell (только * не надо ескейпить).expr 1 + 2 * 3
Немного психологии и тервера
В данной реализации N-Back используются символы из небольшого алфавита с равной вероятностью. Если увеличить алфавит, то совпадения станут очень редки и играть станет слишком скучно. Можно сделать генерацию символов более сложным марковским процессом (вероятность зависит от меняющегося состояния), который сделает совпадения достаточно частыми. Здесь есть еще один интересный момент — на сколько мозг умеет анализировать скрытые марковские модели и учитывать баесову вероятность при распознавании совпадений. Вместо символов можно использовать картинки (китайские иероглифы или химические формулы — совмещаем тренировку с заучиванием) или звуки.
set nback {aa aa} set last "" proc getnext {} { global nback last set nbach [lappend [lset $nback 0] $last] set last [rand] return $last } proc first {} { global nback last eq $last [lindex $nback 0] }
N в названии N-Back задается начальной длиной списка. Мне N==2 хватает :-).
Начальные значения истории выбраны не совпадающими с символами алфавита для упрощения. Так как за красивостями я не гнался, первые N-1 символов, генерящихся для заполнения буфера, пойдут в счетчик угаданных.
set stats {0 0 0 0} proc update k { global stats lset $stats $k [1+ [lindex $stats $k]] set stats }
Процедура подсчета статистики. 1+ — название функции прибавления единицы. В массиве stats хранятся число успешно замеченных несовпадений, ошибочно замеченных несовпадений, ошибочно замеченных совпадений и успешно замеченных совпадений. Такая последовательность была выбрана из удобства реализации, но оказалась удобной и для восприятия.
Следующий код я скаргокультил из оригинального script.hcl
proc PrintLn {g txt} { global MIDL $g clear $g string [list 4 $MIDL] $txt nw } proc EventHandler {c e} { global last MIDL set reason [$e cget -reason] if {!= 5 $reason} {return} set MIDL [/ [$c cget -height] 2] set keycode [$e cget -keycode] set res [first] set sym [getnext] set g [$c graphics] set k [eq 49 $keycode] if {eq $k $res} { PrintLn $g "[update [+ $res $res $k]] Ok $last" } else { PrintLn $g "[update [+ $res $res $k]] Fail $last" } } set c [lcdui.canvas -title "N-Back" -fullscreen 1 -eventhandler EventHandler] $c setcurrent
Здесь можно наблюдать объектно-ориентированные свойства Tcl — объект маскируется под процедуру, сохраненную в переменной, а сообщения маркируются строковыми константами в аргументах. «Конструктор» lcdui.canvas создает «объект canvas» и вешает на него обработчик событий. Мой обработчик распознает события с причиной 5 (нажание на клавишу). Клавиша «1» обрабатывается как замеченное совпадение, остальные — как замеченное несовпадение. Не уверен, что выбор удачный, но экспериментировать лень — процедура загрузки этого на телефон самое сложное во всей разработке :-).
В общем Hecl можно рекомендовать для быстрой разработки/прототипирования и как встраиваемый в Java-приложения язык, в том числе и для устаревших платформ.
