После того, как я не смог ответить на звонок в дочкином телефоне, я решил что что-то надо сделать. Специалисты утверждают, что еще не все потеряно и с помощью специальных технологий можно не отстать от подрастающего поколения. Одним из таких средств является 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. При желании можно написать
set aaa bbb
set $aaa ccc
и переменной bbb присвоется значение «ccc».
Строки, как и в Unix Shell, обычно не надо заключать в кавычки. Если все таки придется, кроме обычных двойных кавычек можно использовать сбалансированные фигурные скобки ({}) — строки могут быть «вложенными».
Строка разбивается по пробелам (получатся список) и первое слово трактуется как имя процедуры — здесь TCL немного напоминает Lisp. Вложенные в команду вызовы других команд заключаются в квадратные скобки ([]).
Команды + и * — нововведение HECL. В оригинальном TCL приходилось вызывать специальный DSL
expr 1 + 2 * 3
почти как в Shell (только * не надо ескейпить).
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-приложения язык, в том числе и для устаревших платформ.
Автор: potan